@lvce-editor/test-with-playwright 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import '../src/all.js'
1
+ import '../src/parts/RunAllTests/RunAllTests.js'
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@lvce-editor/test-with-playwright",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "",
5
- "main": "src/main.js",
6
- "types": "src/main.d.ts",
5
+ "main": "src/parts/RunAllTests/RunAllTests.js",
7
6
  "type": "module",
8
7
  "bin": "bin/test-with-playwright.js",
9
8
  "repository": {
@@ -14,9 +13,13 @@
14
13
  "author": "",
15
14
  "license": "MIT",
16
15
  "dependencies": {
17
- "@playwright/test": "^1.31.2",
16
+ "@playwright/test": "^1.34.3",
18
17
  "get-port": "^6.1.2",
19
18
  "minimist": "^1.2.8",
20
- "read-pkg-up": "^9.1.0"
19
+ "read-pkg-up": "^9.1.0",
20
+ "verror": "^1.10.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/verror": "^1.10.6"
21
24
  }
22
25
  }
@@ -0,0 +1,16 @@
1
+ import * as TestState from '../TestState/TestState.js'
2
+
3
+ export const closeAll = async () => {
4
+ if (TestState.state.childProcess) {
5
+ TestState.state.childProcess.kill('SIGTERM')
6
+ TestState.state.childProcess = undefined
7
+ }
8
+ if (TestState.state.page) {
9
+ await TestState.state.page.close()
10
+ TestState.state.page = undefined
11
+ }
12
+ if (TestState.state.browser) {
13
+ await TestState.state.browser.close()
14
+ TestState.state.browser = undefined
15
+ }
16
+ }
@@ -0,0 +1,2 @@
1
+ export const ENOENT = 'ENOENT'
2
+ export const E_NO_TEST_FILES = 'E_NO_TEST_FILES'
@@ -0,0 +1,2 @@
1
+ export const Success = 0
2
+ export const Error = 1
@@ -0,0 +1,3 @@
1
+ import getPort from 'get-port'
2
+
3
+ export { getPort }
@@ -0,0 +1,10 @@
1
+ import { dirname } from 'node:path'
2
+ import { readPackageUp } from 'read-pkg-up'
3
+
4
+ export const getRoot = async () => {
5
+ const rootPackageJson = await readPackageUp()
6
+ if (!rootPackageJson) {
7
+ throw new Error('package json not found')
8
+ }
9
+ return dirname(rootPackageJson.path)
10
+ }
@@ -0,0 +1,21 @@
1
+ import { readdir } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
4
+ import * as IsTestFile from '../IsTestFile/IsTestFile.js'
5
+ import VError from 'verror'
6
+ import { NoTestFilesFoundError } from '../NoTestFilesFoundError/NoTestFIlesFoundError.js'
7
+
8
+ /**
9
+ * @param {string} root
10
+ */
11
+ export const getTestFiles = async (root) => {
12
+ try {
13
+ const dirents = await readdir(root)
14
+ return dirents.filter(IsTestFile.isTestFile).map((x) => join(root, x))
15
+ } catch (error) {
16
+ if (error && error.code === ErrorCodes.ENOENT) {
17
+ throw new NoTestFilesFoundError(root)
18
+ }
19
+ throw new VError(error, `Failed to get test files`)
20
+ }
21
+ }
@@ -0,0 +1,7 @@
1
+ import { mkdtemp } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+
5
+ export const getTmpDir = (prefix = 'foo-') => {
6
+ return mkdtemp(join(tmpdir(), prefix))
7
+ }
File without changes
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @param {string} name
3
+ */
4
+ export const isTestFile = (name) => {
5
+ return !name.startsWith('_')
6
+ }
@@ -0,0 +1,51 @@
1
+ import { fork } from 'node:child_process'
2
+ import { mkdir, writeFile } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+ import * as GetRoot from '../GetRoot/GetRoot.js'
5
+ import * as GetTmpDir from '../GetTmpDir/GetTmpDir.js'
6
+ import * as TestState from '../TestState/TestState.js'
7
+
8
+ const getExtensionFolder = async () => {
9
+ const root = TestState.state.root || (await GetRoot.getRoot())
10
+ return join(root, '..', 'extension')
11
+ }
12
+
13
+ export const launchServer = async ({ port, folder, env }) => {
14
+ const { serverPath } = await import('@lvce-editor/server')
15
+ // TODO disable saving state between tests in settings
16
+ const configDir = await GetTmpDir.getTmpDir()
17
+ await mkdir(join(configDir, 'lvce-oss'), { recursive: true })
18
+ await writeFile(
19
+ join(configDir, 'lvce-oss', 'settings.json'),
20
+ JSON.stringify(
21
+ {
22
+ 'workbench.saveStateOnVisibilityChange': false,
23
+ },
24
+ null,
25
+ 2
26
+ )
27
+ )
28
+ const extensionFolder = await getExtensionFolder()
29
+ const childProcess = fork(serverPath, {
30
+ stdio: 'inherit',
31
+ env: {
32
+ ...process.env,
33
+ PORT: port,
34
+ FOLDER: folder,
35
+ ONLY_EXTENSION: extensionFolder,
36
+ XDG_CONFIG_HOME: configDir,
37
+ ...env,
38
+ },
39
+ })
40
+ TestState.state.childProcess = childProcess
41
+ return new Promise((resolve, reject) => {
42
+ const handleMessage = (message) => {
43
+ if (message === 'ready') {
44
+ resolve(undefined)
45
+ } else {
46
+ reject(new Error('expected ready message'))
47
+ }
48
+ }
49
+ childProcess.once('message', handleMessage)
50
+ })
51
+ }
@@ -0,0 +1,8 @@
1
+ import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
2
+
3
+ export class NoTestFilesFoundError extends Error {
4
+ constructor(root) {
5
+ super(`No test files found at ${root}`)
6
+ this.code = ErrorCodes.E_NO_TEST_FILES
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ export const argv = process.argv
2
+
3
+ export const exit = (code) => {
4
+ process.exit(code)
5
+ }
@@ -0,0 +1,56 @@
1
+ import parseArgv from 'minimist'
2
+ import { join } from 'node:path'
3
+ import * as CloseAll from '../CloseAll/CloseAll.js'
4
+ import * as ExitCode from '../ExitCode/ExitCode.js'
5
+ import * as GetRoot from '../GetRoot/GetRoot.js'
6
+ import * as GetTestFiles from '../GetTestFiles/GetTestFiles.js'
7
+ import * as Process from '../Process/Process.js'
8
+ import * as RunTests from '../RunTests/RunTests.js'
9
+ import * as ShouldLogErrorWithStack from '../ShouldLogErrorWithStack/ShouldLogErrorWithStack.js'
10
+ import * as StartAll from '../StartAll/StartAll.js'
11
+
12
+ /**
13
+ *
14
+ * @param {any} options
15
+ */
16
+ const getEnv = (options) => {
17
+ const env = Object.create(null)
18
+ if (options['only-extension']) {
19
+ env['ONLY_EXTENSION'] = options['only-extension']
20
+ }
21
+ if (options['test-path']) {
22
+ env['TEST_PATH'] = options['test-path']
23
+ }
24
+ return env
25
+ }
26
+
27
+ const main = async () => {
28
+ try {
29
+ const argv = Process.argv.slice(2)
30
+ const options = parseArgv(argv)
31
+ const env = getEnv(options)
32
+ const { page, port } = await StartAll.startAll(env)
33
+ const root = await GetRoot.getRoot()
34
+ const testFiles = await GetTestFiles.getTestFiles(join(root, 'src'))
35
+ console.log({ testFiles })
36
+ await RunTests.runTests({
37
+ testFiles,
38
+ options,
39
+ page,
40
+ port,
41
+ })
42
+ } catch (error) {
43
+ console.info('tests failed')
44
+ if (ShouldLogErrorWithStack.shouldLogErrorWithStack(error)) {
45
+ console.error(error)
46
+ } else {
47
+ // @ts-ignore
48
+ console.error(error.message)
49
+ }
50
+ Process.exit(ExitCode.Error)
51
+ } finally {
52
+ await CloseAll.closeAll()
53
+ }
54
+ }
55
+
56
+ main()
@@ -0,0 +1,10 @@
1
+ import { performance } from 'node:perf_hooks'
2
+
3
+ export const runTest = async ({ name, fn }) => {
4
+ const start = performance.now()
5
+ console.info(`[test] running ${name}`)
6
+ await fn()
7
+ const end = performance.now()
8
+ const duration = end - start
9
+ console.info(`[test] passed ${name} in ${duration}ms`)
10
+ }
@@ -0,0 +1,83 @@
1
+ import { expect } from '@playwright/test'
2
+ import { basename } from 'node:path'
3
+ import { performance } from 'node:perf_hooks'
4
+ import * as TestOverlayTimeout from '../TestOverlayTimeout/TestOverlayTimeout.js'
5
+
6
+ /**
7
+ * @param {string} absolutePath
8
+ * @param {number} port
9
+ */
10
+ const getUrlFromTestFile = (absolutePath, port) => {
11
+ const baseName = basename(absolutePath)
12
+ const htmlFileName = baseName.slice(0, -'.js'.length) + '.html'
13
+ return `http://localhost:${port}/tests/${htmlFileName}`
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param {import('@playwright/test').Page} page
19
+ * @param {string} url
20
+ */
21
+ const executeSingleTest = async (page, url) => {
22
+ await page.goto(url)
23
+ const testOverlay = page.locator('#TestOverlay')
24
+ await expect(testOverlay).toBeVisible({
25
+ timeout: TestOverlayTimeout.testOverlayTimeout,
26
+ })
27
+ const text = await testOverlay.textContent()
28
+ const state = await testOverlay.getAttribute('data-state')
29
+ switch (state) {
30
+ case 'pass':
31
+ return {
32
+ status: 'pass',
33
+ }
34
+ case 'skip':
35
+ return {
36
+ status: 'skip',
37
+ }
38
+ case 'fail':
39
+ return {
40
+ status: 'fail',
41
+ error: `${text}`,
42
+ }
43
+ default:
44
+ throw new Error(`unexpected test state: ${state}`)
45
+ }
46
+ }
47
+
48
+ /**
49
+ *
50
+ * @param {{testFiles:any[], options:any, port:number, page: import('@playwright/test').Page}} param0
51
+ */
52
+ export const runTests = async ({ testFiles, options, port, page }) => {
53
+ let skipped = 0
54
+ let passed = 0
55
+ const start = performance.now()
56
+ console.log({ testFiles })
57
+ for (const testFile of testFiles) {
58
+ const url = getUrlFromTestFile(testFile, port)
59
+ const name = basename(testFile)
60
+ const result = await executeSingleTest(page, url)
61
+ switch (result.status) {
62
+ case 'pass':
63
+ console.info(`test passed ${name}`)
64
+ passed++
65
+ break
66
+ case 'skip':
67
+ console.info(`test skipped ${name}`)
68
+ skipped++
69
+ break
70
+ case 'fail':
71
+ throw new Error(`Test Failed ${name}: ${result.error}`)
72
+ }
73
+ }
74
+ const end = performance.now()
75
+ const duration = end - start
76
+ if (skipped) {
77
+ console.info(
78
+ `${passed} tests passed, ${skipped} tests skipped in ${duration}ms`
79
+ )
80
+ } else {
81
+ console.info(`${passed} tests passed in ${duration}ms`)
82
+ }
83
+ }
@@ -0,0 +1,13 @@
1
+ import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
2
+
3
+ /**
4
+ *
5
+ * @param {any} error
6
+ * @returns
7
+ */
8
+ export const shouldLogErrorWithStack = (error) => {
9
+ if (error && error.code === ErrorCodes.E_NO_TEST_FILES) {
10
+ return false
11
+ }
12
+ return true
13
+ }
@@ -0,0 +1,18 @@
1
+ import * as GetPort from '../GetPort/GetPort.js'
2
+ import * as LaunchServer from '../LaunchServer/LaunchServer.js'
3
+ import * as StartBrowser from '../StartBrowser/StartBrowser.js'
4
+ import * as TestState from '../TestState/TestState.js'
5
+ import * as Process from '../Process/Process.js'
6
+
7
+ export const startAll = async (env) => {
8
+ const port = await GetPort.getPort()
9
+ await LaunchServer.launchServer({
10
+ port,
11
+ env,
12
+ folder: '',
13
+ })
14
+ TestState.state.port = port
15
+ const headless = Process.argv.includes('--headless')
16
+ const page = await StartBrowser.startBrowser({ headless })
17
+ return { page, port }
18
+ }
@@ -0,0 +1,15 @@
1
+ import { chromium } from '@playwright/test'
2
+ import assert from 'node:assert'
3
+ import * as TestState from '../TestState/TestState.js'
4
+
5
+ export const startBrowser = async ({ headless = false }) => {
6
+ assert(!TestState.state.browser, 'Browser should not be defined')
7
+ console.info('START BROWSER')
8
+ const browser = await chromium.launch({
9
+ headless,
10
+ })
11
+ TestState.state.browser = browser
12
+ const page = await browser.newPage({})
13
+ TestState.state.page = page
14
+ return page
15
+ }
@@ -0,0 +1 @@
1
+ export const testOverlayTimeout = 25_000
@@ -0,0 +1,3 @@
1
+ export const Pass = 'pass'
2
+ export const Skip = 'skip'
3
+ export const Fail = 'fail'
@@ -0,0 +1,23 @@
1
+ export const state = {
2
+ /**
3
+ * @type {import('@playwright/test').Page|undefined}
4
+ */
5
+ page: undefined,
6
+ /**
7
+ * @type {import('@playwright/test').Browser|undefined}
8
+ */
9
+ browser: undefined,
10
+ /**
11
+ * @type {import('child_process').ChildProcess|undefined}
12
+ */
13
+ childProcess: undefined,
14
+ /**
15
+ * @type{boolean}
16
+ */
17
+ runImmediately: true,
18
+ /**
19
+ * @type{any}
20
+ */
21
+ port: 0,
22
+ root: undefined,
23
+ }
package/src/all.js DELETED
@@ -1,126 +0,0 @@
1
- import { expect } from '@playwright/test'
2
- import { readdirSync } from 'node:fs'
3
- import { basename, join } from 'node:path'
4
- import { performance } from 'node:perf_hooks'
5
- import { closeAll, getRoot, runTest, startAll, state } from './main.js'
6
- import parseArgv from 'minimist'
7
-
8
- /**
9
- * @param {string} name
10
- */
11
- const isTestFile = (name) => {
12
- return !name.startsWith('_')
13
- }
14
-
15
- /**
16
- * @param {string} root
17
- */
18
- const getTestFiles = async (root) => {
19
- return readdirSync(root)
20
- .filter(isTestFile)
21
- .map((x) => join(root, x))
22
- }
23
-
24
- /**
25
- * @param {string} absolutePath
26
- * @param {number} port
27
- */
28
- const getUrlFromTestFile = (absolutePath, port) => {
29
- const baseName = basename(absolutePath)
30
- const htmlFileName = baseName.slice(0, -'.js'.length) + '.html'
31
- return `http://localhost:${port}/tests/${htmlFileName}`
32
- }
33
-
34
- /**
35
- *
36
- * @param {import('@playwright/test').Page} page
37
- * @param {string} url
38
- */
39
- const executeSingleTest = async (page, url) => {
40
- await page.goto(url)
41
- const testOverlay = page.locator('#TestOverlay')
42
- await expect(testOverlay).toBeVisible({ timeout: 25_000 })
43
- const text = await testOverlay.textContent()
44
- const state = await testOverlay.getAttribute('data-state')
45
- switch (state) {
46
- case 'pass':
47
- return {
48
- status: 'pass',
49
- }
50
- case 'skip':
51
- return {
52
- status: 'skip',
53
- }
54
- case 'fail':
55
- return {
56
- status: 'fail',
57
- error: `${text}`,
58
- }
59
- default:
60
- throw new Error(`unexpected test state: ${state}`)
61
- }
62
- }
63
-
64
- /**
65
- *
66
- * @param {any} options
67
- */
68
- const getEnv = (options) => {
69
- const env = Object.create(null)
70
- if (options['only-extension']) {
71
- env['ONLY_EXTENSION'] = options['only-extension']
72
- }
73
- if (options['test-path']) {
74
- env['TEST_PATH'] = options['test-path']
75
- }
76
- return env
77
- }
78
-
79
- const main = async () => {
80
- try {
81
- const argv = process.argv.slice(2)
82
- const options = parseArgv(argv)
83
- let skipped = 0
84
- let passed = 0
85
- const env = getEnv(options)
86
- const start = performance.now()
87
- const { page, port } = await startAll(env)
88
- const root = await getRoot()
89
- const testFiles = await getTestFiles(join(root, 'src'))
90
- console.log({ testFiles })
91
- for (const testFile of testFiles) {
92
- const url = getUrlFromTestFile(testFile, port)
93
- const name = basename(testFile)
94
- const result = await executeSingleTest(page, url)
95
- switch (result.status) {
96
- case 'pass':
97
- console.info(`test passed ${name}`)
98
- passed++
99
- break
100
- case 'skip':
101
- console.info(`test skipped ${name}`)
102
- skipped++
103
- break
104
- case 'fail':
105
- throw new Error(`Test Failed ${name}: ${result.error}`)
106
- }
107
- }
108
- const end = performance.now()
109
- const duration = end - start
110
- if (skipped) {
111
- console.info(
112
- `${passed} tests passed, ${skipped} tests skipped in ${duration}ms`
113
- )
114
- } else {
115
- console.info(`${passed} tests passed in ${duration}ms`)
116
- }
117
- } catch (error) {
118
- console.info('tests failed')
119
- console.error(error)
120
- process.exit(1)
121
- } finally {
122
- await closeAll()
123
- }
124
- }
125
-
126
- main()
package/src/main.js DELETED
@@ -1,140 +0,0 @@
1
- import { chromium } from '@playwright/test'
2
- import assert from 'assert'
3
- import { fork } from 'child_process'
4
- import { mkdir, mkdtemp, writeFile } from 'fs/promises'
5
- import getPort from 'get-port'
6
- import { join } from 'node:path'
7
- import { performance } from 'node:perf_hooks'
8
- import { tmpdir } from 'os'
9
- import { dirname } from 'path'
10
- import { readPackageUp } from 'read-pkg-up'
11
-
12
- export const state = {
13
- /**
14
- * @type {import('@playwright/test').Page|undefined}
15
- */
16
- page: undefined,
17
- /**
18
- * @type {import('@playwright/test').Browser|undefined}
19
- */
20
- browser: undefined,
21
- /**
22
- * @type {import('child_process').ChildProcess|undefined}
23
- */
24
- childProcess: undefined,
25
- /**
26
- * @type{boolean}
27
- */
28
- runImmediately: true,
29
- /**
30
- * @type{any}
31
- */
32
- port: 0,
33
- root: undefined,
34
- }
35
-
36
- export const getRoot = async () => {
37
- const rootPackageJson = await readPackageUp()
38
- if (!rootPackageJson) {
39
- throw new Error('package json not found')
40
- }
41
- return dirname(rootPackageJson.path)
42
- }
43
-
44
- const getExtensionFolder = async () => {
45
- const root = state.root || (await getRoot())
46
- return join(root, '..', 'extension')
47
- }
48
-
49
- export const getTmpDir = (prefix = 'foo-') => {
50
- return mkdtemp(join(tmpdir(), prefix))
51
- }
52
-
53
- const launchServer = async ({ port, folder, env }) => {
54
- const { serverPath } = await import('@lvce-editor/server')
55
- // TODO disable saving state between tests in settings
56
- const configDir = await getTmpDir()
57
- await mkdir(join(configDir, 'lvce-oss'), { recursive: true })
58
- await writeFile(
59
- join(configDir, 'lvce-oss', 'settings.json'),
60
- JSON.stringify(
61
- {
62
- 'workbench.saveStateOnVisibilityChange': false,
63
- },
64
- null,
65
- 2
66
- )
67
- )
68
- const extensionFolder = await getExtensionFolder()
69
- const childProcess = fork(serverPath, {
70
- stdio: 'inherit',
71
- env: {
72
- ...process.env,
73
- PORT: port,
74
- FOLDER: folder,
75
- ONLY_EXTENSION: extensionFolder,
76
- XDG_CONFIG_HOME: configDir,
77
- ...env,
78
- },
79
- })
80
- state.childProcess = childProcess
81
- return new Promise((resolve, reject) => {
82
- const handleMessage = (message) => {
83
- if (message === 'ready') {
84
- resolve(undefined)
85
- } else {
86
- reject(new Error('expected ready message'))
87
- }
88
- }
89
- childProcess.once('message', handleMessage)
90
- })
91
- }
92
-
93
- const startBrowser = async ({ port, headless = false }) => {
94
- assert(!state.browser, 'Browser should not be defined')
95
- console.info('START BROWSER')
96
- const browser = await chromium.launch({
97
- headless,
98
- })
99
- state.browser = browser
100
- const page = await browser.newPage({})
101
- state.page = page
102
- return page
103
- }
104
-
105
- export const runTest = async ({ name, fn }) => {
106
- const start = performance.now()
107
- console.info(`[test] running ${name}`)
108
- await fn()
109
- const end = performance.now()
110
- const duration = end - start
111
- console.info(`[test] passed ${name} in ${duration}ms`)
112
- }
113
-
114
- export const startAll = async (env) => {
115
- const port = await getPort()
116
- await launchServer({
117
- port,
118
- env,
119
- folder: '',
120
- })
121
- state.port = port
122
- const headless = process.argv.includes('--headless')
123
- const page = await startBrowser({ port, headless })
124
- return { page, port }
125
- }
126
-
127
- export const closeAll = async () => {
128
- if (state.childProcess) {
129
- state.childProcess.kill('SIGTERM')
130
- state.childProcess = undefined
131
- }
132
- if (state.page) {
133
- await state.page.close()
134
- state.page = undefined
135
- }
136
- if (state.browser) {
137
- await state.browser.close()
138
- state.browser = undefined
139
- }
140
- }