@lvce-editor/test-with-playwright 0.0.27 → 0.1.1
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.
- package/bin/test-with-playwright.js +1 -1
- package/package.json +2 -8
- package/src/parts/Assert/Assert.js +59 -0
- package/src/parts/AssertionError/AssertionError.js +6 -0
- package/src/parts/Callback/Callback.js +39 -0
- package/src/parts/Cli/Cli.js +13 -0
- package/src/parts/CliCommandType/CliCommandType.js +2 -0
- package/src/parts/Command/Command.js +9 -0
- package/src/parts/CommandMap/CommandMap.js +8 -0
- package/src/parts/CommandState/CommandState.js +17 -0
- package/src/parts/FirstNodeWorkerEventType/FirstNodeWorkerEventType.js +5 -0
- package/src/parts/GetFinalResultMessage/GetFinalResultMessage.js +30 -0
- package/src/parts/GetFirstNodeWorkerEvent/GetFirstNodeWorkerEvent.js +28 -0
- package/src/parts/GetOptions/GetOptions.js +18 -0
- package/src/parts/GetPort/GetPort.js +4 -2
- package/src/parts/GetTestWorkerPath/GetTestWorkerPath.js +5 -0
- package/src/parts/GetTests/GetTests.js +10 -0
- package/src/parts/HandleCliArgs/HandleCliArgs.js +24 -0
- package/src/parts/HandleFinalResult/HandleFinalResult.js +8 -0
- package/src/parts/HandleIpc/HandleIpc.js +15 -0
- package/src/parts/HandleResult/HandleResult.js +38 -0
- package/src/parts/Id/Id.js +7 -0
- package/src/parts/IpcError/IpcError.js +6 -0
- package/src/parts/IpcParent/IpcParent.js +9 -0
- package/src/parts/IpcParentModule/IpcParentModule.js +12 -0
- package/src/parts/IpcParentType/IpcParentType.js +2 -0
- package/src/parts/IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js +53 -0
- package/src/parts/IpcParentWithNodeWorker/IpcParentWithNodeWorker.js +41 -0
- package/src/parts/JsonRpc/JsonRpc.js +35 -0
- package/src/parts/JsonRpcVersion/JsonRpcVersion.js +1 -0
- package/src/parts/ParseCliArgs/ParseCliArgs.js +16 -0
- package/src/parts/ParseEnv/ParseEnv.js +10 -0
- package/src/parts/Process/Process.js +15 -0
- package/src/parts/ProcessListeners/ProcessListeners.js +3 -0
- package/src/parts/Root/Root.js +6 -0
- package/src/parts/RunAllTests/RunAllTests.js +17 -51
- package/src/parts/Signal/Signal.js +1 -0
- package/src/parts/TestState/TestState.js +3 -23
- package/src/parts/TestWorkerCommandType/TestWorkerCommandType.js +1 -0
- package/src/parts/CloseAll/CloseAll.js +0 -16
- package/src/parts/ErrorCodes/ErrorCodes.js +0 -2
- package/src/parts/ExitCode/ExitCode.js +0 -2
- package/src/parts/GetRoot/GetRoot.js +0 -10
- package/src/parts/GetTestFiles/GetTestFiles.js +0 -21
- package/src/parts/GetTmpDir/GetTmpDir.js +0 -7
- package/src/parts/InvariantError/InvariantError.js +0 -0
- package/src/parts/IsTestFile/IsTestFile.js +0 -6
- package/src/parts/LaunchServer/LaunchServer.js +0 -51
- package/src/parts/NoTestFilesFoundError/NoTestFIlesFoundError.js +0 -8
- package/src/parts/RunTest/RunTest.js +0 -10
- package/src/parts/RunTests/RunTests.js +0 -83
- package/src/parts/ShouldLogErrorWithStack/ShouldLogErrorWithStack.js +0 -13
- package/src/parts/StartAll/StartAll.js +0 -18
- package/src/parts/StartBrowser/StartBrowser.js +0 -15
- package/src/parts/TestOverlayTimeout/TestOverlayTimeout.js +0 -1
- package/src/parts/TestResultType/TestResultType.js +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
import '../src/parts/
|
|
1
|
+
import '../src/parts/Cli/Cli.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/test-with-playwright",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/parts/RunAllTests/RunAllTests.js",
|
|
6
6
|
"type": "module",
|
|
@@ -13,13 +13,7 @@
|
|
|
13
13
|
"author": "",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@playwright/test": "^1.34.3",
|
|
17
|
-
"get-port": "^6.1.2",
|
|
18
16
|
"minimist": "^1.2.8",
|
|
19
|
-
"
|
|
20
|
-
"verror": "^1.10.1"
|
|
21
|
-
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@types/verror": "^1.10.6"
|
|
17
|
+
"@lvce-editor/test-with-playwright-worker": "0.1.1"
|
|
24
18
|
}
|
|
25
19
|
}
|
|
@@ -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,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,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,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,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,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,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
|
+
}
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
export const argv = process.argv
|
|
2
2
|
|
|
3
|
+
export const env = process.env
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {number} code
|
|
8
|
+
*/
|
|
3
9
|
export const exit = (code) => {
|
|
4
10
|
process.exit(code)
|
|
5
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
|
+
}
|
|
@@ -1,56 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
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'
|
|
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'
|
|
11
7
|
|
|
12
8
|
/**
|
|
13
9
|
*
|
|
14
|
-
* @param {
|
|
10
|
+
* @param {{onlyExtension:string, testPath:string, cwd:string, headless:boolean, timeout:number}} param0
|
|
15
11
|
*/
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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()
|
|
25
22
|
}
|
|
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 @@
|
|
|
1
|
+
export const SIGINT = 'SIGINT'
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
export const
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
}
|
|
1
|
+
export const Pass = 1
|
|
2
|
+
export const Skip = 2
|
|
3
|
+
export const Fail = 3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RunAllTests = 'RunAllTests'
|
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|
|
File without changes
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const testOverlayTimeout = 25_000
|