@rstest/browser 0.8.5 → 0.9.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.
- package/LICENSE-APACHE-2.0 +202 -0
- package/NOTICE +11 -0
- package/dist/361.js +8 -0
- package/dist/augmentExpect.d.ts +73 -0
- package/dist/browser-container/container-static/css/index.5c72297783.css +1 -0
- package/dist/browser-container/container-static/js/{565.226c9ef5.js → 101.36a8ccdf84.js} +4024 -3856
- package/dist/browser-container/container-static/js/101.36a8ccdf84.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.c1d17467.js → index.0687a8142a.js} +742 -692
- package/dist/browser-container/container-static/js/{lib-react.97ee79b0.js → lib-react.dcf2a5e57a.js} +10 -10
- package/dist/browser-container/container-static/js/lib-react.dcf2a5e57a.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +583 -0
- package/dist/browserRpcRegistry.d.ts +18 -0
- package/dist/client/api.d.ts +3 -0
- package/dist/client/browserRpc.d.ts +2 -0
- package/dist/client/dispatchTransport.d.ts +11 -0
- package/dist/client/entry.d.ts +1 -5
- package/dist/client/locator.d.ts +125 -0
- package/dist/client/snapshot.d.ts +0 -6
- package/dist/concurrency.d.ts +12 -0
- package/dist/dispatchCapabilities.d.ts +34 -0
- package/dist/dispatchRouter.d.ts +20 -0
- package/dist/headlessLatestRerunScheduler.d.ts +19 -0
- package/dist/headlessTransport.d.ts +12 -0
- package/dist/index.js +1580 -258
- package/dist/protocol.d.ts +44 -33
- package/dist/providers/index.d.ts +79 -0
- package/dist/providers/playwright/compileLocator.d.ts +3 -0
- package/dist/providers/playwright/dispatchBrowserRpc.d.ts +13 -0
- package/dist/providers/playwright/expectUtils.d.ts +24 -0
- package/dist/providers/playwright/implementation.d.ts +2 -0
- package/dist/providers/playwright/index.d.ts +1 -0
- package/dist/providers/playwright/runtime.d.ts +5 -0
- package/dist/providers/playwright/textMatcher.d.ts +8 -0
- package/dist/rpcProtocol.d.ts +145 -0
- package/dist/runSession.d.ts +33 -0
- package/dist/sessionRegistry.d.ts +34 -0
- package/dist/sourceMap/sourceMapLoader.d.ts +14 -0
- package/dist/watchRerunPlanner.d.ts +21 -0
- package/package.json +15 -10
- package/src/AGENTS.md +128 -0
- package/src/augmentExpect.ts +62 -0
- package/src/browser.ts +3 -0
- package/src/browserRpcRegistry.ts +57 -0
- package/src/client/AGENTS.md +82 -0
- package/src/client/api.ts +213 -0
- package/src/client/browserRpc.ts +86 -0
- package/src/client/dispatchTransport.ts +178 -0
- package/src/client/entry.ts +96 -33
- package/src/client/locator.ts +452 -0
- package/src/client/snapshot.ts +32 -97
- package/src/client/sourceMapSupport.ts +26 -37
- package/src/concurrency.ts +62 -0
- package/src/dispatchCapabilities.ts +162 -0
- package/src/dispatchRouter.ts +82 -0
- package/src/env.d.ts +8 -1
- package/src/headlessLatestRerunScheduler.ts +76 -0
- package/src/headlessTransport.ts +28 -0
- package/src/hostController.ts +1292 -367
- package/src/protocol.ts +66 -31
- package/src/providers/index.ts +103 -0
- package/src/providers/playwright/compileLocator.ts +130 -0
- package/src/providers/playwright/dispatchBrowserRpc.ts +372 -0
- package/src/providers/playwright/expectUtils.ts +57 -0
- package/src/providers/playwright/implementation.ts +33 -0
- package/src/providers/playwright/index.ts +1 -0
- package/src/providers/playwright/runtime.ts +32 -0
- package/src/providers/playwright/textMatcher.ts +10 -0
- package/src/rpcProtocol.ts +220 -0
- package/src/runSession.ts +110 -0
- package/src/sessionRegistry.ts +89 -0
- package/src/sourceMap/sourceMapLoader.ts +96 -0
- package/src/watchRerunPlanner.ts +77 -0
- package/dist/browser-container/container-static/css/index.5a71c757.css +0 -1
- package/dist/browser-container/container-static/js/565.226c9ef5.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.97ee79b0.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/scheduler.5accca0c.js +0 -407
- package/dist/browser-container/scheduler.html +0 -19
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BrowserDispatchRequest,
|
|
3
|
+
BrowserDispatchResponse,
|
|
4
|
+
} from '../protocol';
|
|
5
|
+
import {
|
|
6
|
+
DISPATCH_MESSAGE_TYPE,
|
|
7
|
+
DISPATCH_RESPONSE_TYPE,
|
|
8
|
+
DISPATCH_RPC_REQUEST_TYPE,
|
|
9
|
+
} from '../protocol';
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_RPC_TIMEOUT_MS = 30_000;
|
|
12
|
+
|
|
13
|
+
export const getRpcTimeout = (): number => {
|
|
14
|
+
return (
|
|
15
|
+
window.__RSTEST_BROWSER_OPTIONS__?.rpcTimeout ?? DEFAULT_RPC_TIMEOUT_MS
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const pendingRequests = new Map<
|
|
20
|
+
string,
|
|
21
|
+
{
|
|
22
|
+
resolve: (value: unknown) => void;
|
|
23
|
+
reject: (error: Error) => void;
|
|
24
|
+
staleMessage: string;
|
|
25
|
+
}
|
|
26
|
+
>();
|
|
27
|
+
|
|
28
|
+
let requestIdCounter = 0;
|
|
29
|
+
let messageListenerInitialized = false;
|
|
30
|
+
|
|
31
|
+
export const createRequestId = (prefix: string): string => {
|
|
32
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
33
|
+
return globalThis.crypto.randomUUID();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
requestIdCounter += 1;
|
|
37
|
+
return `${prefix}-${Date.now().toString(36)}-${requestIdCounter.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isDispatchResponse = (
|
|
41
|
+
value: unknown,
|
|
42
|
+
): value is BrowserDispatchResponse => {
|
|
43
|
+
return (
|
|
44
|
+
typeof value === 'object' &&
|
|
45
|
+
value !== null &&
|
|
46
|
+
'requestId' in value &&
|
|
47
|
+
typeof (value as { requestId: unknown }).requestId === 'string'
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const settlePendingRequest = (response: BrowserDispatchResponse): void => {
|
|
52
|
+
const pending = pendingRequests.get(response.requestId);
|
|
53
|
+
if (!pending) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pendingRequests.delete(response.requestId);
|
|
58
|
+
if (response.stale) {
|
|
59
|
+
pending.reject(new Error(pending.staleMessage));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (response.error) {
|
|
63
|
+
pending.reject(new Error(response.error));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
pending.resolve(response.result);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const initMessageListener = (): void => {
|
|
70
|
+
if (messageListenerInitialized) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
messageListenerInitialized = true;
|
|
74
|
+
|
|
75
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
76
|
+
if (event.data?.type === DISPATCH_RESPONSE_TYPE) {
|
|
77
|
+
settlePendingRequest(event.data.payload as BrowserDispatchResponse);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const unwrapDispatchBridgeResult = <T>(
|
|
83
|
+
requestId: string,
|
|
84
|
+
result: unknown,
|
|
85
|
+
staleMessage: string,
|
|
86
|
+
): T => {
|
|
87
|
+
if (!isDispatchResponse(result)) {
|
|
88
|
+
throw new Error('Invalid dispatch bridge response payload.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (result.requestId !== requestId) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Mismatched dispatch response id: expected ${requestId}, got ${result.requestId}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (result.stale) {
|
|
97
|
+
throw new Error(staleMessage);
|
|
98
|
+
}
|
|
99
|
+
if (result.error) {
|
|
100
|
+
throw new Error(result.error);
|
|
101
|
+
}
|
|
102
|
+
return result.result as T;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const dispatchRpc = <T>({
|
|
106
|
+
requestId,
|
|
107
|
+
request,
|
|
108
|
+
timeoutMs,
|
|
109
|
+
timeoutMessage,
|
|
110
|
+
staleMessage,
|
|
111
|
+
}: {
|
|
112
|
+
requestId: string;
|
|
113
|
+
request: BrowserDispatchRequest;
|
|
114
|
+
timeoutMs: number;
|
|
115
|
+
timeoutMessage: string;
|
|
116
|
+
staleMessage: string;
|
|
117
|
+
}): Promise<T> => {
|
|
118
|
+
if (window.parent === window) {
|
|
119
|
+
const dispatchBridge = window.__rstest_dispatch_rpc__;
|
|
120
|
+
if (!dispatchBridge) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
'Dispatch RPC bridge is not available in top-level runner.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return new Promise<T>((resolve, reject) => {
|
|
127
|
+
const timeoutId = setTimeout(() => {
|
|
128
|
+
reject(new Error(timeoutMessage));
|
|
129
|
+
}, timeoutMs);
|
|
130
|
+
|
|
131
|
+
const call = Promise.resolve(dispatchBridge(request)).then((result) =>
|
|
132
|
+
unwrapDispatchBridgeResult<T>(requestId, result, staleMessage),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
call
|
|
136
|
+
.then((result) => {
|
|
137
|
+
clearTimeout(timeoutId);
|
|
138
|
+
resolve(result);
|
|
139
|
+
})
|
|
140
|
+
.catch((error) => {
|
|
141
|
+
clearTimeout(timeoutId);
|
|
142
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
initMessageListener();
|
|
148
|
+
|
|
149
|
+
return new Promise<T>((resolve, reject) => {
|
|
150
|
+
const timeoutId = setTimeout(() => {
|
|
151
|
+
pendingRequests.delete(requestId);
|
|
152
|
+
reject(new Error(timeoutMessage));
|
|
153
|
+
}, timeoutMs);
|
|
154
|
+
|
|
155
|
+
pendingRequests.set(requestId, {
|
|
156
|
+
staleMessage,
|
|
157
|
+
resolve: (value) => {
|
|
158
|
+
clearTimeout(timeoutId);
|
|
159
|
+
resolve(value as T);
|
|
160
|
+
},
|
|
161
|
+
reject: (error) => {
|
|
162
|
+
clearTimeout(timeoutId);
|
|
163
|
+
reject(error);
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
window.parent.postMessage(
|
|
168
|
+
{
|
|
169
|
+
type: DISPATCH_MESSAGE_TYPE,
|
|
170
|
+
payload: {
|
|
171
|
+
type: DISPATCH_RPC_REQUEST_TYPE,
|
|
172
|
+
payload: request,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
'*',
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
};
|
package/src/client/entry.ts
CHANGED
|
@@ -20,9 +20,15 @@ import {
|
|
|
20
20
|
import { normalize } from 'pathe';
|
|
21
21
|
import type {
|
|
22
22
|
BrowserClientMessage,
|
|
23
|
-
|
|
23
|
+
BrowserDispatchRequest,
|
|
24
24
|
BrowserProjectRuntime,
|
|
25
25
|
} from '../protocol';
|
|
26
|
+
import {
|
|
27
|
+
DISPATCH_MESSAGE_TYPE,
|
|
28
|
+
DISPATCH_NAMESPACE_RUNNER,
|
|
29
|
+
DISPATCH_RPC_REQUEST_TYPE,
|
|
30
|
+
RSTEST_CONFIG_MESSAGE_TYPE,
|
|
31
|
+
} from '../protocol';
|
|
26
32
|
import { BrowserSnapshotEnvironment } from './snapshot';
|
|
27
33
|
import {
|
|
28
34
|
findNewScriptUrl,
|
|
@@ -32,14 +38,18 @@ import {
|
|
|
32
38
|
} from './sourceMapSupport';
|
|
33
39
|
|
|
34
40
|
declare global {
|
|
35
|
-
interface Window {
|
|
36
|
-
__RSTEST_BROWSER_OPTIONS__?: BrowserHostConfig;
|
|
37
|
-
__rstest_dispatch__?: (message: BrowserClientMessage) => void;
|
|
38
|
-
}
|
|
39
41
|
// eslint-disable-next-line no-var
|
|
40
42
|
var __coverage__: Record<string, unknown> | undefined;
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
type RunnerLifecycleMethod =
|
|
46
|
+
| 'file-ready'
|
|
47
|
+
| 'suite-start'
|
|
48
|
+
| 'suite-result'
|
|
49
|
+
| 'case-start';
|
|
50
|
+
|
|
51
|
+
let runnerDispatchRequestId = 0;
|
|
52
|
+
|
|
43
53
|
/**
|
|
44
54
|
* Debug logger for browser client.
|
|
45
55
|
* Only logs when debug mode is enabled (DEBUG=rstest on server side).
|
|
@@ -50,10 +60,13 @@ const debugLog = (...args: unknown[]): void => {
|
|
|
50
60
|
}
|
|
51
61
|
};
|
|
52
62
|
|
|
53
|
-
type
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
type RuntimeEnvStore = Record<string, string | undefined>;
|
|
64
|
+
const RSTEST_ENV_SYMBOL = Symbol.for('rstest.env');
|
|
65
|
+
|
|
66
|
+
type GlobalWithRuntimeEnv = typeof globalThis &
|
|
67
|
+
Record<symbol, unknown> & {
|
|
68
|
+
global?: typeof globalThis;
|
|
69
|
+
};
|
|
57
70
|
|
|
58
71
|
const REGEXP_FLAG_PREFIX = 'RSTEST_REGEXP:';
|
|
59
72
|
|
|
@@ -82,36 +95,34 @@ const restoreRuntimeConfig = (
|
|
|
82
95
|
};
|
|
83
96
|
};
|
|
84
97
|
|
|
85
|
-
const
|
|
86
|
-
const globalRef = globalThis as
|
|
98
|
+
const ensureRuntimeEnv = (env: RuntimeConfig['env'] | undefined): void => {
|
|
99
|
+
const globalRef = globalThis as GlobalWithRuntimeEnv;
|
|
87
100
|
if (!globalRef.global) {
|
|
88
101
|
globalRef.global = globalRef;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
cwd: () => '/',
|
|
99
|
-
platform: 'linux',
|
|
100
|
-
nextTick: (cb: (...args: unknown[]) => void, ...args: unknown[]) =>
|
|
101
|
-
queueMicrotask(() => cb(...args)),
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
globalRef.process = processShim as unknown as NodeJS.Process;
|
|
104
|
+
const existingEnv = globalRef[RSTEST_ENV_SYMBOL];
|
|
105
|
+
let runtimeEnv: RuntimeEnvStore;
|
|
106
|
+
if (existingEnv && typeof existingEnv === 'object') {
|
|
107
|
+
runtimeEnv = existingEnv as RuntimeEnvStore;
|
|
108
|
+
} else {
|
|
109
|
+
runtimeEnv = {};
|
|
110
|
+
globalRef[RSTEST_ENV_SYMBOL] = runtimeEnv;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
globalRef.process.env ??= {};
|
|
108
|
-
|
|
109
113
|
if (env) {
|
|
110
114
|
for (const [key, value] of Object.entries(env)) {
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
const normalizedValue =
|
|
116
|
+
typeof value === 'string'
|
|
117
|
+
? value
|
|
118
|
+
: value == null
|
|
119
|
+
? undefined
|
|
120
|
+
: String(value);
|
|
121
|
+
|
|
122
|
+
if (normalizedValue === undefined) {
|
|
123
|
+
delete runtimeEnv[key];
|
|
113
124
|
} else {
|
|
114
|
-
|
|
125
|
+
runtimeEnv[key] = normalizedValue;
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
}
|
|
@@ -206,7 +217,7 @@ const send = (message: BrowserClientMessage): void => {
|
|
|
206
217
|
// If in iframe, send to parent window (container) which will forward to host via RPC
|
|
207
218
|
if (window.parent !== window) {
|
|
208
219
|
window.parent.postMessage(
|
|
209
|
-
{ type:
|
|
220
|
+
{ type: DISPATCH_MESSAGE_TYPE, payload: message },
|
|
210
221
|
'*',
|
|
211
222
|
);
|
|
212
223
|
return;
|
|
@@ -216,6 +227,38 @@ const send = (message: BrowserClientMessage): void => {
|
|
|
216
227
|
window.__rstest_dispatch__?.(message);
|
|
217
228
|
};
|
|
218
229
|
|
|
230
|
+
const dispatchRunnerLifecycle = (
|
|
231
|
+
method: RunnerLifecycleMethod,
|
|
232
|
+
payload: unknown,
|
|
233
|
+
): void => {
|
|
234
|
+
const request: BrowserDispatchRequest = {
|
|
235
|
+
requestId: `runner-lifecycle-${++runnerDispatchRequestId}`,
|
|
236
|
+
namespace: DISPATCH_NAMESPACE_RUNNER,
|
|
237
|
+
method,
|
|
238
|
+
args: payload,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
if (window.parent === window) {
|
|
242
|
+
const dispatchBridge = window.__rstest_dispatch_rpc__;
|
|
243
|
+
if (!dispatchBridge) {
|
|
244
|
+
debugLog(
|
|
245
|
+
'[Runner] Missing dispatch bridge for lifecycle method:',
|
|
246
|
+
method,
|
|
247
|
+
);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
void Promise.resolve(dispatchBridge(request)).catch((error: unknown) => {
|
|
251
|
+
debugLog('[Runner] Failed to dispatch lifecycle method:', method, error);
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
send({
|
|
257
|
+
type: DISPATCH_RPC_REQUEST_TYPE,
|
|
258
|
+
payload: request,
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
219
262
|
/** Timeout for waiting for browser config from container (30 seconds) */
|
|
220
263
|
const CONFIG_WAIT_TIMEOUT_MS = 30_000;
|
|
221
264
|
|
|
@@ -231,7 +274,7 @@ const waitForConfig = (): Promise<void> => {
|
|
|
231
274
|
|
|
232
275
|
return new Promise((resolve, reject) => {
|
|
233
276
|
const handleMessage = (event: MessageEvent) => {
|
|
234
|
-
if (event.data?.type ===
|
|
277
|
+
if (event.data?.type === RSTEST_CONFIG_MESSAGE_TYPE) {
|
|
235
278
|
window.__RSTEST_BROWSER_OPTIONS__ = event.data.payload;
|
|
236
279
|
debugLog(
|
|
237
280
|
'[Runner] Received config from container:',
|
|
@@ -324,6 +367,7 @@ const run = async () => {
|
|
|
324
367
|
// Support reading testFile and testNamePattern from URL parameters
|
|
325
368
|
const urlParams = new URLSearchParams(window.location.search);
|
|
326
369
|
const urlTestFile = urlParams.get('testFile');
|
|
370
|
+
const urlRunId = urlParams.get('runId');
|
|
327
371
|
const urlTestNamePattern = urlParams.get('testNamePattern');
|
|
328
372
|
|
|
329
373
|
if (urlTestFile && options) {
|
|
@@ -334,6 +378,13 @@ const run = async () => {
|
|
|
334
378
|
};
|
|
335
379
|
}
|
|
336
380
|
|
|
381
|
+
if (urlRunId && options) {
|
|
382
|
+
options = {
|
|
383
|
+
...options,
|
|
384
|
+
runId: urlRunId,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
337
388
|
// Override testNamePattern from URL parameter if provided
|
|
338
389
|
if (urlTestNamePattern && options) {
|
|
339
390
|
options = {
|
|
@@ -404,7 +455,7 @@ const run = async () => {
|
|
|
404
455
|
}
|
|
405
456
|
|
|
406
457
|
const runtimeConfig = restoreRuntimeConfig(projectRuntime.runtimeConfig);
|
|
407
|
-
|
|
458
|
+
ensureRuntimeEnv(runtimeConfig.env);
|
|
408
459
|
|
|
409
460
|
// Get this project's setup loaders and test context
|
|
410
461
|
const currentSetupLoaders =
|
|
@@ -556,6 +607,18 @@ const run = async () => {
|
|
|
556
607
|
let failedTestsCount = 0;
|
|
557
608
|
|
|
558
609
|
const runnerHooks: RunnerHooks = {
|
|
610
|
+
onTestFileReady: async (test) => {
|
|
611
|
+
dispatchRunnerLifecycle('file-ready', test);
|
|
612
|
+
},
|
|
613
|
+
onTestSuiteStart: async (test) => {
|
|
614
|
+
dispatchRunnerLifecycle('suite-start', test);
|
|
615
|
+
},
|
|
616
|
+
onTestSuiteResult: async (result) => {
|
|
617
|
+
dispatchRunnerLifecycle('suite-result', result);
|
|
618
|
+
},
|
|
619
|
+
onTestCaseStart: async (test) => {
|
|
620
|
+
dispatchRunnerLifecycle('case-start', test);
|
|
621
|
+
},
|
|
559
622
|
onTestCaseResult: async (result) => {
|
|
560
623
|
if (result.status === 'fail') {
|
|
561
624
|
failedTestsCount++;
|