Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = {
'install-k6.js',
'.eslintrc.cjs',
'**/__snapshots__/',
'tests',
],
plugins: [
'import',
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release-test-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,8 @@ jobs:
if: startsWith(matrix.platform, 'windows-')
run: |
del certificate.pfx

- name: automated tests
if: startsWith(matrix.platform, 'macos-')
run: |
npm run flow-test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,6 @@ resources/win/x86_64/k6

# Sentry Config File
.env.sentry-build-plugin

# tests
test-results
2 changes: 1 addition & 1 deletion forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const config: ForgeConfig = {
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: true,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
Expand Down
80 changes: 80 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"publish": "electron-forge publish",
"lint": "eslint --ext .ts,.tsx .",
"test": "vitest run",
"flow-test": "playwright test -- tests",
"test:watch": "vitest watch",
"format": "prettier --ignore-unknown --write \"./**/*\"",
"prepare": "husky && husky install"
Expand All @@ -33,6 +34,7 @@
"@electron-forge/plugin-vite": "^7.4.0",
"@electron-forge/publisher-github": "^7.4.0",
"@electron/fuses": "^1.8.0",
"@playwright/test": "^1.52.0",
"@tanstack/eslint-plugin-query": "^5.53.0",
"@testing-library/react": "^16.0.1",
"@types/chrome": "^0.0.287",
Expand All @@ -51,6 +53,7 @@
"@typescript-eslint/types": "^8.21.0",
"dotenv": "^16.4.7",
"electron": "30.0.8",
"electron-playwright-helpers": "^1.7.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
Expand Down Expand Up @@ -106,6 +109,7 @@
"@typescript-eslint/typescript-estree": "^8.21.0",
"@vitejs/plugin-react": "^4.3.1",
"allotment": "^1.20.2",
"astring": "^1.9.0",
"chokidar": "^4.0.3",
"clsx": "^2.1.1",
"constrained-editor-plugin": "^1.3.0",
Expand Down
24 changes: 2 additions & 22 deletions src/main/script.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { parse, TSESTree as ts } from '@typescript-eslint/typescript-estree'
import { generate } from 'astring'
import { app, dialog, BrowserWindow } from 'electron'
import { readFile, writeFile, unlink } from 'fs/promises'
import { spawn, ChildProcessWithoutNullStreams } from 'node:child_process'
import path from 'path'
import { format } from 'prettier'
// eslint-disable-next-line import/default
import estree from 'prettier/plugins/estree'
import readline from 'readline/promises'

import {
Expand Down Expand Up @@ -320,25 +318,7 @@ export const enhanceScript = async ({
})
)

return format(script, {
parser: 'k6',
plugins: [
estree,
// This is a custom parser plugin that simply returns our modified AST.
{
parsers: {
k6: {
astFormat: 'estree',
parse: () => {
return scriptAst
},
locStart: () => 0,
locEnd: () => 0,
},
},
},
],
})
return generate(scriptAst)
}

const getSnippetPath = (snippetName: string) => {
Expand Down
132 changes: 132 additions & 0 deletions tests/launch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { test, expect, _electron as electron } from '@playwright/test'
import type { ElectronApplication, Page } from '@playwright/test'
import { findLatestBuild, parseElectronApp } from 'electron-playwright-helpers'

let electronApp: ElectronApplication
let mainWindow: Page
let splashscreenWindow: Page

test.beforeAll(async () => {
const latestBuild = findLatestBuild()
const appInfo = parseElectronApp(latestBuild)

electronApp = await electron.launch({
args: [appInfo.main],
executablePath: appInfo.executable,
})

// splashscreen
splashscreenWindow = await electronApp.firstWindow()
mainWindow = await electronApp.waitForEvent('window')
})

test.afterAll(async () => {
// const pid = electronApp.process().pid
// if (pid !== undefined) {
// process.kill(pid)
// }
await electronApp.close()
// on macos-latest closing might get stuck so we force kill it if we finished testing
// try {
// await electronApp.close()
// } catch (error) {
// console.error('Normal close failed, forcing close:', error)
// const pid = electronApp.process().pid
// if (pid !== undefined) {
// process.kill(pid)
// }
// }
})

test('launch app', async () => {
const title = await mainWindow.title()
expect(title).toBe('Grafana k6 Studio')

const body = splashscreenWindow.locator('body')
// not loading without this await for some reason
await body.count()
expect(body).toBeVisible()
})

test('start recording', async () => {
const testUrl = 'quickpizza.grafana.com'
const recordingButton = mainWindow.getByRole('link', { name: /record flow/i })
await recordingButton.click()

// insert test url
const urlInput = mainWindow.getByRole('textbox', { name: /e\.g\./i })
await urlInput.fill(testUrl)

// start recording button
const startRecording = mainWindow.getByRole('button', {
name: /start recording/i,
})
expect(startRecording).toBeVisible()

await startRecording.click()

// requests are getting recorded, check row appears with quickpizza
const pizzaRows = mainWindow.locator('tr:has-text("quickpizza.grafana.com")')
await pizzaRows.first().waitFor()

expect(pizzaRows.first()).toBeVisible()

// stop recording
const stopRecordingButton = mainWindow.getByRole('button', {
name: /Stop recording/i,
})
expect(stopRecordingButton).toBeVisible()
await stopRecordingButton.click()

// assert we have the create test generator button
const createTestGeneratorButton = mainWindow.getByRole('button', {
name: /Create test generator/i,
})
await createTestGeneratorButton.waitFor()
expect(createTestGeneratorButton).toBeVisible()
})

test('create generator', async () => {
// press the create test generator button
const createTestGeneratorButton = mainWindow.getByRole('button', {
name: /Create test generator/i,
})
await createTestGeneratorButton.click()

// on the allowlist popup, press continue
const allowlistContinue = mainWindow.getByRole('button', { name: 'Continue' })
await allowlistContinue.click()

// add new Custom code rule
const addRule = mainWindow.getByRole('button', { name: 'Add rule' })
await addRule.first().click()

const customCode = mainWindow.getByRole('menuitem', { name: 'Custom code' })
await customCode.click()

// type in the rule
const editor = mainWindow.getByRole('code').nth(1)
await editor.click()
await mainWindow.keyboard.type("console.log('hello test')")

// save the generator
const save = mainWindow.getByRole('button', { name: 'Save generator' })
await save.click()

// validate script
const scriptTab = mainWindow.getByRole('tab', { name: 'Script' })
await scriptTab.click()

const validate = mainWindow.getByRole('button', { name: 'Validate' })
await validate.click()

// close button of Validator dialog is visible
const closeValidator = mainWindow.getByRole('button', { name: 'Close' })
await closeValidator.waitFor()
expect(closeValidator).toBeVisible()
await closeValidator.click()

// go back to Home
const home = mainWindow.getByRole('link', { name: 'Home' })
await home.first().click()
})
3 changes: 2 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import viteTsconfigPaths from 'vite-tsconfig-paths'
import { defineConfig } from 'vitest/config'
import { defineConfig, configDefaults } from 'vitest/config'

export default defineConfig({
plugins: [viteTsconfigPaths()],
test: {
includeSource: ['src/**/*.{js,ts}'],
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'tests/*'],
},
define: {
'import.meta.vitest': 'undefined',
Expand Down
Loading