@lvce-editor/test-with-playwright 0.0.26 → 0.1.0

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.
Files changed (41) hide show
  1. package/bin/test-with-playwright.js +1 -1
  2. package/package.json +3 -7
  3. package/src/parts/Assert/Assert.js +59 -0
  4. package/src/parts/AssertionError/AssertionError.js +6 -0
  5. package/src/parts/Callback/Callback.js +39 -0
  6. package/src/parts/Cli/Cli.js +13 -0
  7. package/src/parts/CliCommandType/CliCommandType.js +2 -0
  8. package/src/parts/Command/Command.js +9 -0
  9. package/src/parts/CommandMap/CommandMap.js +8 -0
  10. package/src/parts/CommandState/CommandState.js +17 -0
  11. package/src/parts/FirstNodeWorkerEventType/FirstNodeWorkerEventType.js +5 -0
  12. package/src/parts/GetFinalResultMessage/GetFinalResultMessage.js +30 -0
  13. package/src/parts/GetFirstNodeWorkerEvent/GetFirstNodeWorkerEvent.js +28 -0
  14. package/src/parts/GetOptions/GetOptions.js +18 -0
  15. package/src/parts/GetPort/GetPort.js +5 -0
  16. package/src/parts/GetTestWorkerPath/GetTestWorkerPath.js +5 -0
  17. package/src/parts/GetTests/GetTests.js +10 -0
  18. package/src/parts/HandleCliArgs/HandleCliArgs.js +24 -0
  19. package/src/parts/HandleFinalResult/HandleFinalResult.js +8 -0
  20. package/src/parts/HandleIpc/HandleIpc.js +15 -0
  21. package/src/parts/HandleResult/HandleResult.js +38 -0
  22. package/src/parts/Id/Id.js +7 -0
  23. package/src/parts/IpcError/IpcError.js +6 -0
  24. package/src/parts/IpcParent/IpcParent.js +9 -0
  25. package/src/parts/IpcParentModule/IpcParentModule.js +12 -0
  26. package/src/parts/IpcParentType/IpcParentType.js +2 -0
  27. package/src/parts/IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js +53 -0
  28. package/src/parts/IpcParentWithNodeWorker/IpcParentWithNodeWorker.js +41 -0
  29. package/src/parts/JsonRpc/JsonRpc.js +35 -0
  30. package/src/parts/JsonRpcVersion/JsonRpcVersion.js +1 -0
  31. package/src/parts/ParseCliArgs/ParseCliArgs.js +16 -0
  32. package/src/parts/ParseEnv/ParseEnv.js +10 -0
  33. package/src/parts/Process/Process.js +20 -0
  34. package/src/parts/ProcessListeners/ProcessListeners.js +3 -0
  35. package/src/parts/Root/Root.js +6 -0
  36. package/src/parts/RunAllTests/RunAllTests.js +22 -0
  37. package/src/parts/Signal/Signal.js +1 -0
  38. package/src/parts/TestState/TestState.js +3 -0
  39. package/src/parts/TestWorkerCommandType/TestWorkerCommandType.js +1 -0
  40. package/src/all.js +0 -126
  41. package/src/main.js +0 -140
@@ -1 +1 @@
1
- import '../src/all.js'
1
+ import '../src/parts/Cli/Cli.js'
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@lvce-editor/test-with-playwright",
3
- "version": "0.0.26",
3
+ "version": "0.1.0",
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,6 @@
14
13
  "author": "",
15
14
  "license": "MIT",
16
15
  "dependencies": {
17
- "@playwright/test": "^1.34.2",
18
- "get-port": "^6.1.2",
19
- "minimist": "^1.2.8",
20
- "read-pkg-up": "^9.1.0"
16
+ "minimist": "^1.2.8"
21
17
  }
22
18
  }
@@ -0,0 +1,59 @@
1
+ import { AssertionError } from '../AssertionError/AssertionError.js'
2
+
3
+ const getType = (value) => {
4
+ switch (typeof value) {
5
+ case 'number':
6
+ return 'number'
7
+ case 'function':
8
+ return 'function'
9
+ case 'string':
10
+ return 'string'
11
+ case 'object':
12
+ if (value === null) {
13
+ return 'null'
14
+ }
15
+ if (Array.isArray(value)) {
16
+ return 'array'
17
+ }
18
+ return 'object'
19
+ case 'boolean':
20
+ return 'boolean'
21
+ default:
22
+ return 'unknown'
23
+ }
24
+ }
25
+
26
+ export const object = (value) => {
27
+ const type = getType(value)
28
+ if (type !== 'object') {
29
+ throw new AssertionError('expected value to be of type object')
30
+ }
31
+ }
32
+
33
+ export const number = (value) => {
34
+ const type = getType(value)
35
+ if (type !== 'number') {
36
+ throw new AssertionError('expected value to be of type number')
37
+ }
38
+ }
39
+
40
+ export const array = (value) => {
41
+ const type = getType(value)
42
+ if (type !== 'array') {
43
+ throw new AssertionError('expected value to be of type array')
44
+ }
45
+ }
46
+
47
+ export const string = (value) => {
48
+ const type = getType(value)
49
+ if (type !== 'string') {
50
+ throw new AssertionError('expected value to be of type string')
51
+ }
52
+ }
53
+
54
+ export const boolean = (value) => {
55
+ const type = getType(value)
56
+ if (type !== 'boolean') {
57
+ throw new AssertionError('expected value to be of type boolean')
58
+ }
59
+ }
@@ -0,0 +1,6 @@
1
+ export class AssertionError extends Error {
2
+ constructor(message) {
3
+ super(message)
4
+ this.name = 'AssertionError'
5
+ }
6
+ }
@@ -0,0 +1,39 @@
1
+ import * as Id from '../Id/Id.js'
2
+
3
+ export const state = {
4
+ callbacks: Object.create(null),
5
+ onceListeners: new Set(),
6
+ }
7
+
8
+ export const registerPromise = () => {
9
+ const id = Id.create()
10
+ const promise = new Promise((resolve, reject) => {
11
+ state.callbacks[id] = { resolve, reject }
12
+ })
13
+ return { id, promise }
14
+ }
15
+
16
+ // TODO merge resolve and resolveEmpty
17
+ export const resolve = (id, args) => {
18
+ const { callbacks } = state
19
+ if (!(id in callbacks)) {
20
+ console.warn(`callback ${id} may already be disposed`)
21
+ return
22
+ }
23
+ callbacks[id].resolve(args)
24
+ delete callbacks[id]
25
+ }
26
+
27
+ export const reject = (id, error) => {
28
+ const { callbacks } = state
29
+ if (!(id in callbacks)) {
30
+ console.warn(`callback (rejected) ${id} may already be disposed`)
31
+ return
32
+ }
33
+ callbacks[id].reject(error)
34
+ delete callbacks[id]
35
+ }
36
+
37
+ export const isAllEmpty = () => {
38
+ return Object.keys(state.callbacks).length === 0 && state.onceListeners.size === 0
39
+ }
@@ -0,0 +1,13 @@
1
+ import * as HandleCliArgs from '../HandleCliArgs/HandleCliArgs.js'
2
+ import * as Process from '../Process/Process.js'
3
+ import * as ProcessListeners from '../ProcessListeners/ProcessListeners.js'
4
+ import * as CommandState from '../CommandState/CommandState.js'
5
+ import * as CommandMap from '../CommandMap/CommandMap.js'
6
+
7
+ export const main = () => {
8
+ Process.on('uncaughtExceptionMonitor', ProcessListeners.handleUncaughtExceptionMonitor)
9
+ CommandState.registerCommands(CommandMap.commandMap)
10
+ HandleCliArgs.handleCliArgs({ argv: Process.argv, env: Process.env })
11
+ }
12
+
13
+ main()
@@ -0,0 +1,2 @@
1
+ export const HandleResult = 'HandleResult'
2
+ export const HandleFinalResult = 'HandleFinalResult'
@@ -0,0 +1,9 @@
1
+ import * as CommandState from '../CommandState/CommandState.js'
2
+
3
+ export const execute = (command, ...args) => {
4
+ const fn = CommandState.getCommand(command)
5
+ if (!fn) {
6
+ throw new Error(`Command not found ${command}`)
7
+ }
8
+ return fn(...args)
9
+ }
@@ -0,0 +1,8 @@
1
+ import * as CliCommandType from '../CliCommandType/CliCommandType.js'
2
+ import * as HandleFinalResult from '../HandleFinalResult/HandleFinalResult.js'
3
+ import * as HandleResult from '../HandleResult/HandleResult.js'
4
+
5
+ export const commandMap = {
6
+ [CliCommandType.HandleResult]: HandleResult.handleResult,
7
+ [CliCommandType.HandleFinalResult]: HandleFinalResult.handleFinalResult,
8
+ }
@@ -0,0 +1,17 @@
1
+ export const state = {
2
+ commands: Object.create(null),
3
+ }
4
+
5
+ export const registerCommand = (key, fn) => {
6
+ state.commands[key] = fn
7
+ }
8
+
9
+ export const registerCommands = (commandMap) => {
10
+ for (const [key, value] of Object.entries(commandMap)) {
11
+ registerCommand(key, value)
12
+ }
13
+ }
14
+
15
+ export const getCommand = (key) => {
16
+ return state.commands[key]
17
+ }
@@ -0,0 +1,5 @@
1
+ export const Exit = 'exit'
2
+
3
+ export const Error = 'error'
4
+
5
+ export const Message = 'message'
@@ -0,0 +1,30 @@
1
+ export const getFinalResultMessage = (passed, skipped, failed, duration) => {
2
+ if (passed === 0 && skipped === 0 && failed === 0) {
3
+ return `no tests found`
4
+ }
5
+ if (passed === 0 && skipped === 0 && failed === 1) {
6
+ return `${passed} test failed in ${duration}ms`
7
+ }
8
+ if (passed === 0 && skipped === 1 && failed === 0) {
9
+ return `${skipped} test skipped in ${duration}ms`
10
+ }
11
+ if (passed === 0 && skipped === 1 && failed === 1) {
12
+ return `${skipped} test skipped, ${failed} test failed in ${duration}ms`
13
+ }
14
+ if (passed === 1 && skipped === 0 && failed === 0) {
15
+ return `${passed} test passed in ${duration}ms`
16
+ }
17
+ if (passed === 1 && skipped === 0 && failed === 1) {
18
+ return `${passed} test passed, ${failed} test failed in ${duration}ms`
19
+ }
20
+ if (passed === 1 && skipped === 1 && failed === 0) {
21
+ return `${passed} test passed, ${skipped} test skipped in ${duration}ms`
22
+ }
23
+ if (passed === 1 && skipped === 1 && failed === 1) {
24
+ return `${passed} test passed, ${skipped} test skipped, ${failed} test failed in ${duration}ms`
25
+ }
26
+ if (passed > 1 && skipped === 0 && failed === 0) {
27
+ return `${passed} tests passed in ${duration}ms`
28
+ }
29
+ return `${passed} tests passed, ${skipped} tests skipped, ${failed} tests failed in ${duration}ms`
30
+ }
@@ -0,0 +1,28 @@
1
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
2
+
3
+ export const getFirstNodeWorkerEvent = async (worker) => {
4
+ const { type, event } = await new Promise((resolve, reject) => {
5
+ const cleanup = (value) => {
6
+ worker.off('message', handleMessage)
7
+ worker.off('exit', handleExit)
8
+ worker.off('error', handleError)
9
+ resolve(value)
10
+ }
11
+ const handleMessage = (event) => {
12
+ cleanup({
13
+ type: FirstNodeWorkerEventType.Message,
14
+ event,
15
+ })
16
+ }
17
+ const handleExit = (event) => {
18
+ cleanup({ type: FirstNodeWorkerEventType.Exit, event })
19
+ }
20
+ const handleError = (event) => {
21
+ cleanup({ type: FirstNodeWorkerEventType.Error, event })
22
+ }
23
+ worker.on('message', handleMessage)
24
+ worker.on('exit', handleExit)
25
+ worker.on('error', handleError)
26
+ })
27
+ return { type, event }
28
+ }
@@ -0,0 +1,18 @@
1
+ import * as ParseCliArgs from '../ParseCliArgs/ParseCliArgs.js'
2
+ import * as ParseEnv from '../ParseEnv/ParseEnv.js'
3
+
4
+ const defaultOptions = {
5
+ testPath: '',
6
+ extensionPath: '',
7
+ headless: false,
8
+ }
9
+
10
+ export const getOptions = ({ argv, env }) => {
11
+ const parsedEnv = ParseEnv.parseEnv(env)
12
+ const parsedArgs = ParseCliArgs.parseCliArgs(argv)
13
+ return {
14
+ ...defaultOptions,
15
+ ...parsedEnv,
16
+ ...parsedArgs,
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ import _getPort from 'get-port'
2
+
3
+ export const getPort = () => {
4
+ return _getPort()
5
+ }
@@ -0,0 +1,5 @@
1
+ import { testWorkerPath } from '@lvce-editor/test-with-playwright-worker'
2
+
3
+ export const getTestWorkerPath = () => {
4
+ return testWorkerPath
5
+ }
@@ -0,0 +1,10 @@
1
+ import { readdir } from 'fs/promises'
2
+ import { join } from 'path'
3
+
4
+ /**
5
+ * @param {string} testSrc
6
+ */
7
+ export const getTests = async (testSrc) => {
8
+ const dirents = await readdir(testSrc)
9
+ return dirents
10
+ }
@@ -0,0 +1,24 @@
1
+ import * as GetOptions from '../GetOptions/GetOptions.js'
2
+ import * as RunAllTests from '../RunAllTests/RunAllTests.js'
3
+
4
+ /**
5
+ *
6
+ * @param {{argv:string[], env:any}} param0
7
+ */
8
+ export const handleCliArgs = async ({ argv, env }) => {
9
+ const cwd = process.cwd()
10
+ const options = GetOptions.getOptions({ argv, env })
11
+ const onlyExtension = options.onlyExtension
12
+ const testPath = options.testPath
13
+ const headless = options.headless
14
+ const timeout = 30_000
15
+ // TODO
16
+ // console.log({ argv, env })
17
+ await RunAllTests.runAllTests({
18
+ onlyExtension,
19
+ testPath,
20
+ cwd,
21
+ headless,
22
+ timeout,
23
+ })
24
+ }
@@ -0,0 +1,8 @@
1
+ import * as GetFinalResultMessage from '../GetFinalResultMessage/GetFinalResultMessage.js'
2
+
3
+ export const handleFinalResult = (finalResult) => {
4
+ const { passed, failed, skipped, start, end } = finalResult
5
+ const duration = end - start
6
+ const message = GetFinalResultMessage.getFinalResultMessage(passed, skipped, failed, duration)
7
+ console.info(message)
8
+ }
@@ -0,0 +1,15 @@
1
+ import * as Callback from '../Callback/Callback.js'
2
+ import * as Command from '../Command/Command.js'
3
+
4
+ export function handleIpc(ipc) {
5
+ const handleMessage = async (message) => {
6
+ if ('result' in message || 'error' in message) {
7
+ Callback.resolve(message.id, message)
8
+ return
9
+ }
10
+ if ('method' in message) {
11
+ await Command.execute(message.method, ...message.params)
12
+ }
13
+ }
14
+ ipc.on('message', handleMessage)
15
+ }
@@ -0,0 +1,38 @@
1
+ import * as TestState from '../TestState/TestState.js'
2
+
3
+ const getDuration = (start, end) => {
4
+ return end - start
5
+ }
6
+
7
+ const handleResultPassed = (result) => {
8
+ const { name, start, end } = result
9
+ const duration = getDuration(start, end)
10
+ console.info(`test passed ${name} in ${duration}ms`)
11
+ }
12
+
13
+ const handleResultSkipped = (result) => {
14
+ const { name } = result
15
+ console.info(`test skipped ${name}`)
16
+ }
17
+
18
+ const handleResultFailed = (result) => {
19
+ const { name, error } = result
20
+ console.error(`Test Failed ${name}: ${error}`)
21
+ }
22
+
23
+ export const handleResult = (result) => {
24
+ const { status } = result
25
+ switch (status) {
26
+ case TestState.Pass:
27
+ handleResultPassed(result)
28
+ break
29
+ case TestState.Skip:
30
+ handleResultSkipped(result)
31
+ break
32
+ case TestState.Fail:
33
+ handleResultFailed(result)
34
+ break
35
+ default:
36
+ throw new Error(`unexpected test state: ${status}`)
37
+ }
38
+ }
@@ -0,0 +1,7 @@
1
+ export const state = {
2
+ id: 0,
3
+ }
4
+
5
+ export const create = () => {
6
+ return ++state.id
7
+ }
@@ -0,0 +1,6 @@
1
+ export class IpcError extends Error {
2
+ constructor(message) {
3
+ super(message)
4
+ this.name = 'IpcError'
5
+ }
6
+ }
@@ -0,0 +1,9 @@
1
+ import * as IpcParentModule from '../IpcParentModule/IpcParentModule.js'
2
+
3
+ export const create = async ({ method, ...options }) => {
4
+ const module = await IpcParentModule.getModule(method)
5
+ // @ts-ignore
6
+ const rawIpc = await module.create(options)
7
+ const ipc = module.wrap(rawIpc)
8
+ return ipc
9
+ }
@@ -0,0 +1,12 @@
1
+ import * as IpcParentType from '../IpcParentType/IpcParentType.js'
2
+
3
+ export const getModule = (method) => {
4
+ switch (method) {
5
+ case IpcParentType.NodeWorker:
6
+ return import('../IpcParentWithNodeWorker/IpcParentWithNodeWorker.js')
7
+ case IpcParentType.NodeForkedProcess:
8
+ return import('../IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js')
9
+ default:
10
+ throw new Error('unexpected ipc type')
11
+ }
12
+ }
@@ -0,0 +1,2 @@
1
+ export const NodeWorker = 1
2
+ export const NodeForkedProcess = 2
@@ -0,0 +1,53 @@
1
+ import * as Assert from '../Assert/Assert.js'
2
+ import { fork } from 'node:child_process'
3
+ import * as GetFirstNodeChildProcessEvent from '../GetFirstNodeChildProcessEvent/GetFirstNodeChildProcessEvent.js'
4
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
5
+ import { ChildProcessError } from '../ChildProcessError/ChildProcessError.js'
6
+ import { VError } from '../VError/VError.js'
7
+
8
+ export const create = async ({ path, argv = [], env, execArgv = [], stdio = 'inherit', name = 'child process' }) => {
9
+ try {
10
+ Assert.string(path)
11
+ const actualArgv = ['--ipc-type=node-forked-process', ...argv]
12
+ const childProcess = fork(path, actualArgv, {
13
+ env,
14
+ execArgv,
15
+ stdio: 'pipe',
16
+ })
17
+ const { type, event, stdout, stderr } = await GetFirstNodeChildProcessEvent.getFirstNodeChildProcessEvent(childProcess)
18
+ if (type === FirstNodeWorkerEventType.Exit) {
19
+ throw new ChildProcessError(stderr)
20
+ }
21
+ if (type === FirstNodeWorkerEventType.Error) {
22
+ throw new Error(`child process had an error ${event}`)
23
+ }
24
+ if (stdio === 'inherit' && childProcess.stdout && childProcess.stderr) {
25
+ childProcess.stdout.pipe(process.stdout)
26
+ childProcess.stderr.pipe(process.stderr)
27
+ }
28
+ return childProcess
29
+ } catch (error) {
30
+ throw new VError(error, `Failed to launch ${name}`)
31
+ }
32
+ }
33
+
34
+ export const wrap = (childProcess) => {
35
+ return {
36
+ childProcess,
37
+ on(event, listener) {
38
+ this.childProcess.on(event, listener)
39
+ },
40
+ off(event, listener) {
41
+ this.childProcess.off(event, listener)
42
+ },
43
+ send(message) {
44
+ this.childProcess.send(message)
45
+ },
46
+ sendAndTransfer(message, handle) {
47
+ this.childProcess.send(message, handle)
48
+ },
49
+ dispose() {
50
+ this.childProcess.kill()
51
+ },
52
+ }
53
+ }
@@ -0,0 +1,41 @@
1
+ import { Worker } from 'node:worker_threads'
2
+ import * as Assert from '../Assert/Assert.js'
3
+ import * as FirstNodeWorkerEventType from '../FirstNodeWorkerEventType/FirstNodeWorkerEventType.js'
4
+ import * as GetFirstNodeWorkerEvent from '../GetFirstNodeWorkerEvent/GetFirstNodeWorkerEvent.js'
5
+ import { IpcError } from '../IpcError/IpcError.js'
6
+
7
+ export const create = async ({ path, argv, env, execArgv }) => {
8
+ Assert.string(path)
9
+ const actualArgv = ['--ipc-type=node-worker', ...argv]
10
+ const worker = new Worker(path, {
11
+ argv: actualArgv,
12
+ env,
13
+ execArgv,
14
+ })
15
+ const { type, event } = await GetFirstNodeWorkerEvent.getFirstNodeWorkerEvent(worker)
16
+ if (type === FirstNodeWorkerEventType.Error) {
17
+ throw event
18
+ }
19
+ if (type === FirstNodeWorkerEventType.Exit) {
20
+ throw new IpcError('worker exited unexpectedly')
21
+ }
22
+ return worker
23
+ }
24
+
25
+ export const wrap = (worker) => {
26
+ return {
27
+ worker,
28
+ on(event, listener) {
29
+ this.worker.on(event, listener)
30
+ },
31
+ send(message) {
32
+ this.worker.postMessage(message)
33
+ },
34
+ sendAndTransfer(message, transfer) {
35
+ this.worker.postMessage(message, transfer)
36
+ },
37
+ dispose() {
38
+ this.worker.terminate()
39
+ },
40
+ }
41
+ }
@@ -0,0 +1,35 @@
1
+ import * as Callback from '../Callback/Callback.js'
2
+ import * as JsonRpcVersion from '../JsonRpcVersion/JsonRpcVersion.js'
3
+
4
+ export const send = (transport, method, ...params) => {
5
+ transport.send({
6
+ jsonrpc: JsonRpcVersion.Two,
7
+ method,
8
+ params,
9
+ })
10
+ }
11
+
12
+ export const invoke = (ipc, method, ...params) => {
13
+ const { id, promise } = Callback.registerPromise()
14
+ ipc.send({
15
+ jsonrpc: JsonRpcVersion.Two,
16
+ method,
17
+ params,
18
+ id,
19
+ })
20
+ return promise
21
+ }
22
+
23
+ export const invokeAndTransfer = (ipc, handle, method, ...params) => {
24
+ const { id, promise } = Callback.registerPromise()
25
+ ipc.sendAndTransfer(
26
+ {
27
+ jsonrpc: JsonRpcVersion.Two,
28
+ method,
29
+ params,
30
+ id,
31
+ },
32
+ handle
33
+ )
34
+ return promise
35
+ }
@@ -0,0 +1 @@
1
+ export const Two = '2.0'
@@ -0,0 +1,16 @@
1
+ import parseArgv from 'minimist'
2
+
3
+ export const parseCliArgs = (argv) => {
4
+ const parsed = parseArgv(argv)
5
+ const result = Object.create(null)
6
+ if (parsed.headless) {
7
+ result.headless = true
8
+ }
9
+ if (parsed['only-extension']) {
10
+ result.onlyExtension = parsed['only-extension']
11
+ }
12
+ if (parsed['test-path']) {
13
+ result.testPath = parsed['test-path']
14
+ }
15
+ return result
16
+ }
@@ -0,0 +1,10 @@
1
+ export const parseEnv = (env) => {
2
+ const options = Object.create(null)
3
+ if (env['ONLY_EXTENSION']) {
4
+ options['onlyExtension'] = env['ONLY_EXTENSION']
5
+ }
6
+ if (env['TEST_PATH']) {
7
+ options['testPath'] = env['TEST_PATH']
8
+ }
9
+ return options
10
+ }
@@ -0,0 +1,20 @@
1
+ export const argv = process.argv
2
+
3
+ export const env = process.env
4
+
5
+ /**
6
+ *
7
+ * @param {number} code
8
+ */
9
+ export const exit = (code) => {
10
+ process.exit(code)
11
+ }
12
+
13
+ /**
14
+ *
15
+ * @param {string} event
16
+ * @param {any} listener
17
+ */
18
+ export const on = (event, listener) => {
19
+ process.on(event, listener)
20
+ }
@@ -0,0 +1,3 @@
1
+ export const handleUncaughtExceptionMonitor = (error) => {
2
+ console.log(`[test] uncaught exception ${error}`)
3
+ }
@@ -0,0 +1,6 @@
1
+ import { dirname, join } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+
4
+ const __dirname = dirname(fileURLToPath(import.meta.url))
5
+
6
+ export const root = join(__dirname, '..', '..', '..', '..', '..')
@@ -0,0 +1,22 @@
1
+ import * as GetTestWorkerPath from '../GetTestWorkerPath/GetTestWorkerPath.js'
2
+ import * as HandleIpc from '../HandleIpc/HandleIpc.js'
3
+ import * as IpcParent from '../IpcParent/IpcParent.js'
4
+ import * as IpcParentType from '../IpcParentType/IpcParentType.js'
5
+ import * as JsonRpc from '../JsonRpc/JsonRpc.js'
6
+ import * as TestWorkerCommandType from '../TestWorkerCommandType/TestWorkerCommandType.js'
7
+
8
+ /**
9
+ *
10
+ * @param {{onlyExtension:string, testPath:string, cwd:string, headless:boolean, timeout:number}} param0
11
+ */
12
+ export const runAllTests = async ({ onlyExtension, testPath, cwd, headless, timeout }) => {
13
+ const path = GetTestWorkerPath.getTestWorkerPath()
14
+ const ipc = await IpcParent.create({
15
+ method: IpcParentType.NodeWorker,
16
+ path,
17
+ argv: [],
18
+ })
19
+ HandleIpc.handleIpc(ipc)
20
+ await JsonRpc.invoke(ipc, TestWorkerCommandType.RunAllTests, onlyExtension, testPath, cwd, headless, timeout)
21
+ ipc.dispose()
22
+ }
@@ -0,0 +1 @@
1
+ export const SIGINT = 'SIGINT'
@@ -0,0 +1,3 @@
1
+ export const Pass = 1
2
+ export const Skip = 2
3
+ export const Fail = 3
@@ -0,0 +1 @@
1
+ export const RunAllTests = 'RunAllTests'
package/src/all.js DELETED
@@ -1,126 +0,0 @@
1
- import { expect } from '@playwright/test'
2
- import parseArgv from 'minimist'
3
- import { readdirSync } from 'node:fs'
4
- import { basename, join } from 'node:path'
5
- import { performance } from 'node:perf_hooks'
6
- import { closeAll, getRoot, startAll } from './main.js'
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 'node:assert'
3
- import { fork } from 'node:child_process'
4
- import { mkdir, mkdtemp, writeFile } from 'node: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 'node:os'
9
- import { dirname } from 'node: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
- }