@rstest/browser 0.11.8 → 0.11.9
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/dist/client/protocol.d.ts +3 -1
- package/dist/client/runner.js +288 -167
- package/dist/index.js +134 -62
- package/dist/protocol.d.ts +3 -1
- package/package.json +5 -5
|
@@ -23,6 +23,7 @@ export type BrowserProjectRuntime = {
|
|
|
23
23
|
name: string;
|
|
24
24
|
environmentName: string;
|
|
25
25
|
projectRoot: string;
|
|
26
|
+
/** Setup modules are re-evaluated per file, so they cannot share a worker page. */ hasSetupFiles: boolean;
|
|
26
27
|
runtimeConfig: SerializedRuntimeConfig;
|
|
27
28
|
viewport?: BrowserViewport;
|
|
28
29
|
};
|
|
@@ -33,7 +34,7 @@ export type BrowserProjectRuntime = {
|
|
|
33
34
|
testPath: string;
|
|
34
35
|
projectName: string;
|
|
35
36
|
};
|
|
36
|
-
export type FileCleanupDispatchMethod = 'start' | 'end';
|
|
37
|
+
export type FileCleanupDispatchMethod = 'start' | 'end' | 'worker-start' | 'worker-end';
|
|
37
38
|
export type FileCleanupDispatchPayload = {
|
|
38
39
|
projectName: string;
|
|
39
40
|
result?: TestFileResult;
|
|
@@ -82,6 +83,7 @@ export type BrowserHostConfig = {
|
|
|
82
83
|
updateSnapshot: SnapshotUpdateState;
|
|
83
84
|
};
|
|
84
85
|
testFile?: string;
|
|
86
|
+
/** Test files assigned to one browser worker session. */ testFiles?: string[];
|
|
85
87
|
/**
|
|
86
88
|
* The run identity this document executes under — the runner's SOLE identity
|
|
87
89
|
* source, adopted once at boot and stamped on every outbound message. Headed:
|
package/dist/client/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { projectSetupLoaders, projectTestContexts, projects } from "__rstest_virtual_browser_manifest__";
|
|
2
|
-
import { RSTEST_API_GLOBAL_KEY, RSTEST_ENV_SYMBOL_KEY, RSTEST_IMPORT_META_GLOBAL_KEY, SNAPSHOT_HEADER, createBrowserTaskContext, createRstestRuntime, formatConsoleArgs, globalApis, setRealTimers, unwrapRegex } from "@rstest/core/internal/browser-runtime";
|
|
2
|
+
import { FIXTURE_CLEANUP_TIMEOUT_MS, RSTEST_API_GLOBAL_KEY, RSTEST_ENV_SYMBOL_KEY, RSTEST_IMPORT_META_GLOBAL_KEY, SNAPSHOT_HEADER, cleanupWorkerFixtures as browser_runtime_cleanupWorkerFixtures, createBrowserTaskContext, createRstestRuntime, formatConsoleArgs, getRealTimers, globalApis, setRealTimers, unwrapRegex } from "@rstest/core/internal/browser-runtime";
|
|
3
3
|
import { normalize } from "pathe";
|
|
4
4
|
import { AnyMap, TraceMap, encodedMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
5
5
|
import convert_source_map from "convert-source-map";
|
|
@@ -186,6 +186,42 @@ class BrowserSnapshotEnvironment {
|
|
|
186
186
|
const debugLog = (...args)=>{
|
|
187
187
|
if (window.__RSTEST_BROWSER_OPTIONS__?.debug) console.log(...args);
|
|
188
188
|
};
|
|
189
|
+
const cloneCoverage = (coverage)=>JSON.parse(JSON.stringify(coverage));
|
|
190
|
+
const subtractCounters = (current, previous)=>Object.fromEntries(Object.entries(current).map(([key, value])=>[
|
|
191
|
+
key,
|
|
192
|
+
value - (previous?.[key] ?? 0)
|
|
193
|
+
]));
|
|
194
|
+
const getCoverageDelta = (current, previous)=>Object.fromEntries(Object.entries(current).map(([path, file])=>{
|
|
195
|
+
const previousFile = previous?.[path];
|
|
196
|
+
return [
|
|
197
|
+
path,
|
|
198
|
+
{
|
|
199
|
+
...file,
|
|
200
|
+
s: subtractCounters(file.s, previousFile?.s),
|
|
201
|
+
f: subtractCounters(file.f, previousFile?.f),
|
|
202
|
+
b: Object.fromEntries(Object.entries(file.b).map(([key, values])=>[
|
|
203
|
+
key,
|
|
204
|
+
values.map((value, index)=>value - (previousFile?.b[key]?.[index] ?? 0))
|
|
205
|
+
]))
|
|
206
|
+
}
|
|
207
|
+
];
|
|
208
|
+
}));
|
|
209
|
+
const cleanupWorkerFixturesWithTimeout = async ()=>{
|
|
210
|
+
const realTimers = getRealTimers();
|
|
211
|
+
let timer;
|
|
212
|
+
try {
|
|
213
|
+
await Promise.race([
|
|
214
|
+
browser_runtime_cleanupWorkerFixtures(),
|
|
215
|
+
new Promise((_, reject)=>{
|
|
216
|
+
timer = realTimers.setTimeout?.(()=>{
|
|
217
|
+
reject(new Error(`Worker fixture cleanup did not finish within ${FIXTURE_CLEANUP_TIMEOUT_MS}ms`));
|
|
218
|
+
}, FIXTURE_CLEANUP_TIMEOUT_MS);
|
|
219
|
+
})
|
|
220
|
+
]);
|
|
221
|
+
} finally{
|
|
222
|
+
if (timer) realTimers.clearTimeout?.(timer);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
189
225
|
const RSTEST_ENV_SYMBOL = Symbol.for(RSTEST_ENV_SYMBOL_KEY);
|
|
190
226
|
const installRuntimeGlobals = (runtime, runtimeConfig)=>{
|
|
191
227
|
Object.assign(globalThis, {
|
|
@@ -289,8 +325,9 @@ const waitForConfig = ()=>{
|
|
|
289
325
|
if (window.parent === window || window.__RSTEST_BROWSER_OPTIONS__) return Promise.resolve();
|
|
290
326
|
return new Promise((resolve, reject)=>{
|
|
291
327
|
const handleMessage = (event)=>{
|
|
292
|
-
|
|
293
|
-
|
|
328
|
+
const payload = event.data?.payload;
|
|
329
|
+
if (event.data?.type === RSTEST_CONFIG_MESSAGE_TYPE && 'string' == typeof payload?.runId) {
|
|
330
|
+
window.__RSTEST_BROWSER_OPTIONS__ = payload;
|
|
294
331
|
debugLog('[Runner] Received config from container:', event.data.payload);
|
|
295
332
|
window.removeEventListener('message', handleMessage);
|
|
296
333
|
resolve();
|
|
@@ -364,6 +401,9 @@ const run = async ()=>{
|
|
|
364
401
|
setRealTimers();
|
|
365
402
|
await preloadRunnerSourceMap();
|
|
366
403
|
const targetTestFile = options.testFile;
|
|
404
|
+
const targetTestFiles = options.testFiles?.length ? options.testFiles : targetTestFile ? [
|
|
405
|
+
targetTestFile
|
|
406
|
+
] : void 0;
|
|
367
407
|
const currentProject = targetTestFile ? findProjectForTestFile(targetTestFile, projects) : projects["0"];
|
|
368
408
|
if (!currentProject) {
|
|
369
409
|
send({
|
|
@@ -404,12 +444,7 @@ const run = async ()=>{
|
|
|
404
444
|
for (const loadSetup of currentSetupLoaders)await loadSetup();
|
|
405
445
|
};
|
|
406
446
|
let testKeysToRun;
|
|
407
|
-
|
|
408
|
-
const key = toContextKey(targetTestFile, currentProject.projectRoot);
|
|
409
|
-
testKeysToRun = [
|
|
410
|
-
key
|
|
411
|
-
];
|
|
412
|
-
} else testKeysToRun = currentTestContext.getTestKeys();
|
|
447
|
+
testKeysToRun = targetTestFiles ? targetTestFiles.map((testFile)=>toContextKey(testFile, currentProject.projectRoot)) : currentTestContext.getTestKeys();
|
|
413
448
|
const executionMode = options.mode || 'run';
|
|
414
449
|
if ('collect' === executionMode) {
|
|
415
450
|
for (const key of testKeysToRun){
|
|
@@ -478,183 +513,269 @@ const run = async ()=>{
|
|
|
478
513
|
};
|
|
479
514
|
window.addEventListener('error', onWindowError);
|
|
480
515
|
window.addEventListener('unhandledrejection', onUnhandledRejection);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
outputModule: false,
|
|
501
|
-
environment: 'browser',
|
|
502
|
-
currentTask: taskStack[0],
|
|
503
|
-
testPath,
|
|
504
|
-
distPath: testPath,
|
|
505
|
-
snapshotOptions: {
|
|
506
|
-
updateSnapshot: options.snapshot.updateSnapshot,
|
|
507
|
-
snapshotEnvironment: new BrowserSnapshotEnvironment(),
|
|
508
|
-
snapshotFormat: runtimeConfig.snapshotFormat
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
const syncCurrentTask = ()=>{
|
|
512
|
-
workerState.currentTask = taskStack[taskStack.length - 1];
|
|
513
|
-
};
|
|
514
|
-
const removeTaskFromStack = (taskId)=>{
|
|
515
|
-
const taskIndex = taskStack.findLastIndex((task)=>task.taskId === taskId);
|
|
516
|
-
if (taskIndex < 0) return;
|
|
517
|
-
taskStack.splice(taskIndex, 1);
|
|
518
|
-
syncCurrentTask();
|
|
519
|
-
};
|
|
520
|
-
const runtime = await createRstestRuntime(workerState, {
|
|
521
|
-
taskContext
|
|
522
|
-
});
|
|
523
|
-
installRuntimeGlobals(runtime, runtimeConfig);
|
|
524
|
-
let failedTestsCount = 0;
|
|
525
|
-
const dispatchFileCleanup = async (method, result, waitForAcknowledgement = true)=>{
|
|
526
|
-
const requestId = createRequestId(`file-cleanup-${method}`);
|
|
527
|
-
const request = {
|
|
528
|
-
requestId,
|
|
529
|
-
namespace: DISPATCH_NAMESPACE_FILE_CLEANUP,
|
|
530
|
-
method,
|
|
531
|
-
args: {
|
|
532
|
-
projectName: projectRuntime.name,
|
|
533
|
-
result,
|
|
534
|
-
runId: getRunIdentity(),
|
|
516
|
+
const keepWorkerFixtures = false === runtimeConfig.isolate && true !== projectRuntime.hasSetupFiles;
|
|
517
|
+
let restoreWorkerConsole;
|
|
518
|
+
let workerCleanupFailed = false;
|
|
519
|
+
let workerCleanupAttempted = false;
|
|
520
|
+
let setupListeners;
|
|
521
|
+
let previousIstanbulCoverage;
|
|
522
|
+
try {
|
|
523
|
+
for(let fileIndex = 0; fileIndex < testKeysToRun.length; fileIndex++){
|
|
524
|
+
const key = testKeysToRun[fileIndex];
|
|
525
|
+
const testPath = toAbsolutePath(key, currentProject.projectRoot);
|
|
526
|
+
options = {
|
|
527
|
+
...options,
|
|
528
|
+
testFile: testPath
|
|
529
|
+
};
|
|
530
|
+
window.__RSTEST_BROWSER_OPTIONS__ = options;
|
|
531
|
+
const taskStack = [
|
|
532
|
+
{
|
|
533
|
+
taskId: getFileTaskId(testPath),
|
|
534
|
+
taskType: 'file',
|
|
535
535
|
testPath
|
|
536
536
|
}
|
|
537
|
+
];
|
|
538
|
+
const taskContext = createBrowserTaskContext();
|
|
539
|
+
const shouldInterceptConsole = !runtimeConfig.disableConsoleIntercept || true === runtimeConfig.silent || 'passed-only' === runtimeConfig.silent;
|
|
540
|
+
const restoreConsole = shouldInterceptConsole ? interceptConsole(projectRuntime.name, ()=>taskContext.getCurrent() ?? taskStack[taskStack.length - 1], runtimeConfig.disableConsoleIntercept ? false : runtimeConfig.printConsoleTrace ?? false) : ()=>{};
|
|
541
|
+
if (keepWorkerFixtures) restoreWorkerConsole = restoreConsole;
|
|
542
|
+
const workerState = {
|
|
543
|
+
project: projectRuntime.name,
|
|
544
|
+
projectRoot: projectRuntime.projectRoot,
|
|
545
|
+
rootPath: options.rootPath,
|
|
546
|
+
runtimeConfig,
|
|
547
|
+
taskId: 0,
|
|
548
|
+
buildId: 0,
|
|
549
|
+
outputModule: false,
|
|
550
|
+
environment: 'browser',
|
|
551
|
+
currentTask: taskStack[0],
|
|
552
|
+
testPath,
|
|
553
|
+
distPath: testPath,
|
|
554
|
+
snapshotOptions: {
|
|
555
|
+
updateSnapshot: options.snapshot.updateSnapshot,
|
|
556
|
+
snapshotEnvironment: new BrowserSnapshotEnvironment(),
|
|
557
|
+
snapshotFormat: runtimeConfig.snapshotFormat
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const syncCurrentTask = ()=>{
|
|
561
|
+
workerState.currentTask = taskStack[taskStack.length - 1];
|
|
562
|
+
};
|
|
563
|
+
const removeTaskFromStack = (taskId)=>{
|
|
564
|
+
const taskIndex = taskStack.findLastIndex((task)=>task.taskId === taskId);
|
|
565
|
+
if (taskIndex < 0) return;
|
|
566
|
+
taskStack.splice(taskIndex, 1);
|
|
567
|
+
syncCurrentTask();
|
|
568
|
+
};
|
|
569
|
+
let runtime;
|
|
570
|
+
try {
|
|
571
|
+
runtime = await createRstestRuntime(workerState, {
|
|
572
|
+
taskContext
|
|
573
|
+
});
|
|
574
|
+
installRuntimeGlobals(runtime, runtimeConfig);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
restoreConsole();
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
579
|
+
let failedTestsCount = 0;
|
|
580
|
+
const dispatchFileCleanup = async (method, result, waitForAcknowledgement = true)=>{
|
|
581
|
+
const requestId = createRequestId(`file-cleanup-${method}`);
|
|
582
|
+
const request = {
|
|
583
|
+
requestId,
|
|
584
|
+
namespace: DISPATCH_NAMESPACE_FILE_CLEANUP,
|
|
585
|
+
method,
|
|
586
|
+
args: {
|
|
587
|
+
projectName: projectRuntime.name,
|
|
588
|
+
result,
|
|
589
|
+
runId: getRunIdentity(),
|
|
590
|
+
testPath
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
if (!waitForAcknowledgement) return void sendDispatchRequest(request);
|
|
594
|
+
await dispatchRpc({
|
|
595
|
+
requestId,
|
|
596
|
+
request,
|
|
597
|
+
timeoutMs: getRpcTimeout('framework'),
|
|
598
|
+
timeoutMessage: `File cleanup ${method} acknowledgement timed out for ${testPath}.`,
|
|
599
|
+
staleMessage: `File cleanup ${method} became stale for ${testPath}.`
|
|
600
|
+
});
|
|
537
601
|
};
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
602
|
+
const cleanupWorkerFixtures = async (result)=>{
|
|
603
|
+
await dispatchFileCleanup('worker-start', result);
|
|
604
|
+
try {
|
|
605
|
+
await cleanupWorkerFixturesWithTimeout();
|
|
606
|
+
} finally{
|
|
607
|
+
await dispatchFileCleanup('worker-end');
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
const updateIstanbulCoverage = (result)=>{
|
|
611
|
+
if (!globalThis.__coverage__) return;
|
|
612
|
+
const currentCoverage = globalThis.__coverage__;
|
|
613
|
+
result.coverage = getCoverageDelta(currentCoverage, previousIstanbulCoverage);
|
|
614
|
+
previousIstanbulCoverage = cloneCoverage(currentCoverage);
|
|
615
|
+
};
|
|
616
|
+
const runnerHooks = {
|
|
617
|
+
onFileCleanupStart: async (result)=>{
|
|
618
|
+
if (result && globalThis.__coverage__) result.coverage = globalThis.__coverage__;
|
|
619
|
+
await dispatchFileCleanup('start', result, window.parent !== window);
|
|
620
|
+
},
|
|
621
|
+
onFileCleanupEnd: ()=>dispatchFileCleanup('end', void 0, window.parent !== window),
|
|
622
|
+
onSnapshotSetupStart: async ()=>{
|
|
623
|
+
setRpcPhase('framework');
|
|
624
|
+
},
|
|
625
|
+
onSnapshotSetupEnd: async ()=>{
|
|
626
|
+
setRpcPhase('test');
|
|
627
|
+
},
|
|
628
|
+
onSnapshotFinishStart: async ()=>{
|
|
629
|
+
setRpcPhase('framework');
|
|
630
|
+
},
|
|
631
|
+
onSnapshotFinishEnd: async ()=>{
|
|
632
|
+
setRpcPhase('test');
|
|
633
|
+
},
|
|
634
|
+
onTestFileReady: async (test)=>{
|
|
635
|
+
dispatchRunnerLifecycle('file-ready', test);
|
|
636
|
+
},
|
|
637
|
+
onTestSuiteStart: async (test)=>{
|
|
638
|
+
taskStack.push({
|
|
639
|
+
taskId: test.testId,
|
|
640
|
+
taskName: test.name,
|
|
641
|
+
taskParentNames: test.parentNames,
|
|
642
|
+
taskType: 'suite',
|
|
643
|
+
testPath: test.testPath
|
|
644
|
+
});
|
|
645
|
+
syncCurrentTask();
|
|
646
|
+
dispatchRunnerLifecycle('suite-start', test);
|
|
647
|
+
},
|
|
648
|
+
onTestSuiteResult: async (result)=>{
|
|
649
|
+
removeTaskFromStack(result.testId);
|
|
650
|
+
dispatchRunnerLifecycle('suite-result', result);
|
|
651
|
+
},
|
|
652
|
+
onTestCaseStart: async (test)=>{
|
|
653
|
+
taskStack.push({
|
|
654
|
+
taskId: test.testId,
|
|
655
|
+
taskName: test.name,
|
|
656
|
+
taskParentNames: test.parentNames,
|
|
657
|
+
taskType: 'case',
|
|
658
|
+
testPath: test.testPath
|
|
659
|
+
});
|
|
660
|
+
syncCurrentTask();
|
|
661
|
+
dispatchRunnerLifecycle('case-start', test);
|
|
662
|
+
},
|
|
663
|
+
onTestCaseResult: async (result)=>{
|
|
664
|
+
removeTaskFromStack(result.testId);
|
|
665
|
+
if ('fail' === result.status) failedTestsCount++;
|
|
666
|
+
send({
|
|
667
|
+
type: 'case-result',
|
|
668
|
+
payload: result
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
getCountOfFailedTests: async ()=>failedTestsCount
|
|
672
|
+
};
|
|
673
|
+
send({
|
|
674
|
+
type: 'file-start',
|
|
675
|
+
payload: {
|
|
676
|
+
testPath,
|
|
677
|
+
projectName: projectRuntime.name
|
|
678
|
+
}
|
|
545
679
|
});
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (result && globalThis.__coverage__) result.coverage = globalThis.__coverage__;
|
|
550
|
-
await dispatchFileCleanup('start', result, window.parent !== window);
|
|
551
|
-
},
|
|
552
|
-
onFileCleanupEnd: ()=>dispatchFileCleanup('end', void 0, window.parent !== window),
|
|
553
|
-
onSnapshotSetupStart: async ()=>{
|
|
554
|
-
setRpcPhase('framework');
|
|
555
|
-
},
|
|
556
|
-
onSnapshotSetupEnd: async ()=>{
|
|
557
|
-
setRpcPhase('test');
|
|
558
|
-
},
|
|
559
|
-
onSnapshotFinishStart: async ()=>{
|
|
680
|
+
const unhandledErrors = [];
|
|
681
|
+
activeUnhandledErrors = unhandledErrors;
|
|
682
|
+
try {
|
|
560
683
|
setRpcPhase('framework');
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
684
|
+
if (setupListeners) runtime.runner.setRootSuiteListeners(setupListeners);
|
|
685
|
+
await loadSetupFiles();
|
|
686
|
+
setupListeners ??= runtime.runner.getRootSuiteListeners();
|
|
687
|
+
const beforeScripts = getScriptUrls();
|
|
688
|
+
await currentTestContext.loadTest(key);
|
|
689
|
+
const afterScripts = getScriptUrls();
|
|
690
|
+
const chunkUrl = findNewScriptUrl(beforeScripts, afterScripts);
|
|
691
|
+
if (chunkUrl) await preloadTestFileSourceMap(chunkUrl);
|
|
692
|
+
const result = await runtime.runner.runTests(testPath, runnerHooks, runtime.api);
|
|
693
|
+
const cleanupBeforeFileComplete = !keepWorkerFixtures || fileIndex === testKeysToRun.length - 1;
|
|
694
|
+
if (cleanupBeforeFileComplete) {
|
|
695
|
+
workerCleanupAttempted = true;
|
|
696
|
+
try {
|
|
697
|
+
await cleanupWorkerFixtures(result);
|
|
698
|
+
} catch (cleanupError) {
|
|
699
|
+
const formattedCleanupError = cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError));
|
|
700
|
+
result.status = 'fail';
|
|
701
|
+
result.errors = [
|
|
702
|
+
...result.errors ?? [],
|
|
703
|
+
{
|
|
704
|
+
fullStack: true,
|
|
705
|
+
message: `Worker fixture cleanup failed: ${formattedCleanupError.message}`,
|
|
706
|
+
name: formattedCleanupError.name,
|
|
707
|
+
stack: formattedCleanupError.stack
|
|
708
|
+
}
|
|
709
|
+
];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
updateIstanbulCoverage(result);
|
|
713
|
+
for(let i = 0; i < 2; i++)await new Promise((resolve)=>{
|
|
714
|
+
setTimeout(resolve, 0);
|
|
590
715
|
});
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
716
|
+
if (unhandledErrors.length > 0) {
|
|
717
|
+
result.status = 'fail';
|
|
718
|
+
result.errors = [
|
|
719
|
+
...result.errors ?? [],
|
|
720
|
+
...unhandledErrors.map((error)=>({
|
|
721
|
+
name: error.name,
|
|
722
|
+
message: error.message,
|
|
723
|
+
stack: error.stack
|
|
724
|
+
}))
|
|
725
|
+
];
|
|
726
|
+
}
|
|
597
727
|
send({
|
|
598
|
-
type: '
|
|
728
|
+
type: 'file-complete',
|
|
599
729
|
payload: result
|
|
600
730
|
});
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (unhandledErrors.length > 0) {
|
|
626
|
-
result.status = 'fail';
|
|
627
|
-
result.errors = [
|
|
628
|
-
...result.errors ?? [],
|
|
629
|
-
...unhandledErrors.map((error)=>({
|
|
630
|
-
name: error.name,
|
|
631
|
-
message: error.message,
|
|
632
|
-
stack: error.stack
|
|
633
|
-
}))
|
|
634
|
-
];
|
|
731
|
+
} catch (_error) {
|
|
732
|
+
let error = _error instanceof Error ? _error : new Error(String(_error));
|
|
733
|
+
if (!workerCleanupAttempted) try {
|
|
734
|
+
workerCleanupAttempted = true;
|
|
735
|
+
await cleanupWorkerFixtures();
|
|
736
|
+
} catch (cleanupError) {
|
|
737
|
+
const formattedCleanupError = cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError));
|
|
738
|
+
error = new Error(`${error.message}\nWorker fixture cleanup failed: ${formattedCleanupError.message}`, {
|
|
739
|
+
cause: error
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
send({
|
|
743
|
+
type: 'fatal',
|
|
744
|
+
payload: {
|
|
745
|
+
message: error.message,
|
|
746
|
+
stack: error.stack
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
window.__RSTEST_DONE__ = true;
|
|
750
|
+
return;
|
|
751
|
+
} finally{
|
|
752
|
+
if (keepWorkerFixtures && fileIndex === testKeysToRun.length - 1) restoreWorkerConsole = restoreConsole;
|
|
753
|
+
else restoreConsole();
|
|
754
|
+
activeUnhandledErrors = void 0;
|
|
635
755
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
} catch (
|
|
641
|
-
|
|
756
|
+
}
|
|
757
|
+
} finally{
|
|
758
|
+
if (keepWorkerFixtures && !workerCleanupAttempted) try {
|
|
759
|
+
await cleanupWorkerFixturesWithTimeout();
|
|
760
|
+
} catch (error) {
|
|
761
|
+
workerCleanupFailed = true;
|
|
762
|
+
const cleanupError = error instanceof Error ? error : new Error(String(error));
|
|
642
763
|
send({
|
|
643
764
|
type: 'fatal',
|
|
644
765
|
payload: {
|
|
645
|
-
message:
|
|
646
|
-
stack:
|
|
766
|
+
message: cleanupError.message,
|
|
767
|
+
stack: cleanupError.stack
|
|
647
768
|
}
|
|
648
769
|
});
|
|
649
|
-
window.__RSTEST_DONE__ = true;
|
|
650
|
-
return;
|
|
651
|
-
} finally{
|
|
652
|
-
restoreConsole();
|
|
653
|
-
activeUnhandledErrors = void 0;
|
|
654
770
|
}
|
|
771
|
+
restoreWorkerConsole?.();
|
|
655
772
|
}
|
|
656
773
|
window.removeEventListener('error', onWindowError);
|
|
657
774
|
window.removeEventListener('unhandledrejection', onUnhandledRejection);
|
|
775
|
+
if (workerCleanupFailed) {
|
|
776
|
+
window.__RSTEST_DONE__ = true;
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
658
779
|
send({
|
|
659
780
|
type: 'complete'
|
|
660
781
|
});
|
package/dist/index.js
CHANGED
|
@@ -1844,11 +1844,6 @@ const ignoredKeyWarnings = {
|
|
|
1844
1844
|
isNonDefault: (config)=>'node' !== config.testEnvironment.name,
|
|
1845
1845
|
message: (config)=>`Ignoring testEnvironment '${config.testEnvironment.name}' in browser mode: the browser itself is the test environment.`
|
|
1846
1846
|
},
|
|
1847
|
-
isolate: {
|
|
1848
|
-
isNonDefault: (config)=>false === config.isolate,
|
|
1849
|
-
message: ()=>"Ignoring isolate: false in browser mode: each test file still runs in a fresh context.",
|
|
1850
|
-
browserOnly: true
|
|
1851
|
-
},
|
|
1852
1847
|
detectAsyncLeaks: {
|
|
1853
1848
|
isNonDefault: (config)=>true === config.detectAsyncLeaks,
|
|
1854
1849
|
message: ()=>"Ignoring detectAsyncLeaks in browser mode: it relies on node async_hooks."
|
|
@@ -1917,6 +1912,8 @@ const reportUnsupportedBrowserOptions = (context)=>{
|
|
|
1917
1912
|
}
|
|
1918
1913
|
}
|
|
1919
1914
|
}
|
|
1915
|
+
if (isBrowserOnlyRun && browserProjects.some(({ normalizedConfig: config })=>false === config.isolate && false === config.browser.headless)) warnings.add('Browser mode keeps headed file frames isolated, so worker-scoped fixtures do not span files when browser.headless is false.');
|
|
1916
|
+
if (isBrowserOnlyRun && browserProjects.some(({ normalizedConfig: config })=>false === config.isolate && (config.setupFiles?.length ?? 0) > 0)) warnings.add('Browser projects with setupFiles stay file-isolated, even when isolate is false, so setup modules can run for every file.');
|
|
1920
1917
|
for (const message of warnings)logger.warn(color.yellow(message));
|
|
1921
1918
|
};
|
|
1922
1919
|
const validateBrowserConfig = (context)=>{
|
|
@@ -2981,9 +2978,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
2981
2978
|
const refreshProjectEntries = async ()=>{
|
|
2982
2979
|
validateBrowserConfig(context);
|
|
2983
2980
|
browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
2984
|
-
const updatedShardedEntries = freezeShardedEntries ? shardedEntries : context.normalizedConfig.shard ? await resolveShardedEntries(context
|
|
2985
|
-
silent: true
|
|
2986
|
-
}) : shardedEntries;
|
|
2981
|
+
const updatedShardedEntries = freezeShardedEntries ? shardedEntries : context.normalizedConfig.shard ? await resolveShardedEntries(context) : shardedEntries;
|
|
2987
2982
|
projectEntries = await resolveProjectEntries(context, updatedShardedEntries, browserProjects);
|
|
2988
2983
|
for (const manifestModule of manifestModules)refreshManifestModule(manifestModule);
|
|
2989
2984
|
};
|
|
@@ -4125,26 +4120,25 @@ const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptio
|
|
|
4125
4120
|
dispatchRouter.register(DISPATCH_NAMESPACE_FILE_CLEANUP, async (request)=>{
|
|
4126
4121
|
const { runId } = request;
|
|
4127
4122
|
if (!runId) return;
|
|
4128
|
-
if ('start' === request.method) {
|
|
4123
|
+
if ('start' === request.method || 'worker-start' === request.method) {
|
|
4129
4124
|
const payload = request.args;
|
|
4130
4125
|
runs.armCleanupDeadline(runId, FIXTURE_CLEANUP_TIMEOUT_MS, ()=>{
|
|
4131
4126
|
(async ()=>{
|
|
4132
4127
|
try {
|
|
4133
|
-
await
|
|
4128
|
+
await recoverHeadedContainer();
|
|
4134
4129
|
await handleTestFileComplete(createFileCleanupTimeoutResult({
|
|
4135
4130
|
message: `File fixture cleanup did not finish within ${FIXTURE_CLEANUP_TIMEOUT_MS}ms`,
|
|
4136
4131
|
projectName: payload.projectName,
|
|
4137
4132
|
result: payload.result,
|
|
4138
4133
|
testPath: payload.testPath
|
|
4139
4134
|
}));
|
|
4140
|
-
await recoverHeadedContainer();
|
|
4141
4135
|
runs.resolve(runId);
|
|
4142
4136
|
} catch (error) {
|
|
4143
4137
|
runs.reject(runId, toError(error));
|
|
4144
4138
|
}
|
|
4145
4139
|
})();
|
|
4146
4140
|
});
|
|
4147
|
-
} else if ('end' === request.method) runs.disarmCleanupDeadline(runId);
|
|
4141
|
+
} else if ('end' === request.method || 'worker-end' === request.method) runs.disarmCleanupDeadline(runId);
|
|
4148
4142
|
});
|
|
4149
4143
|
const reloadTestFileAndWait = async (file, testNamePattern)=>{
|
|
4150
4144
|
await startCoverage(file.projectName);
|
|
@@ -4462,6 +4456,10 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4462
4456
|
project.name,
|
|
4463
4457
|
project.projectRoot
|
|
4464
4458
|
]));
|
|
4459
|
+
const projectIsolation = new Map(projectRuntimeConfigs.map((project)=>[
|
|
4460
|
+
project.name,
|
|
4461
|
+
project.hasSetupFiles || project.runtimeConfig?.isolate !== false
|
|
4462
|
+
]));
|
|
4465
4463
|
const runLifecycle = new RunSessionLifecycle();
|
|
4466
4464
|
const sessionRegistry = new RunnerSessionRegistry();
|
|
4467
4465
|
setDispatchPageResolver((target)=>({
|
|
@@ -4505,10 +4503,15 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4505
4503
|
const handler = fileCleanupHandlers.get(sessionId);
|
|
4506
4504
|
if (!handler) return;
|
|
4507
4505
|
const payload = request.args;
|
|
4508
|
-
if ('start' === request.method) handler.start(payload
|
|
4509
|
-
else if ('
|
|
4506
|
+
if ('start' === request.method) await handler.start(payload);
|
|
4507
|
+
else if ('worker-start' === request.method) await handler.workerStart();
|
|
4508
|
+
else if ('end' === request.method) await handler.end(payload);
|
|
4509
|
+
else if ('worker-end' === request.method) await handler.workerEnd();
|
|
4510
4510
|
});
|
|
4511
|
-
const dispatchRunnerMessage = async (run,
|
|
4511
|
+
const dispatchRunnerMessage = async (run, files, sessionId, message)=>{
|
|
4512
|
+
const payload = 'payload' in message ? message.payload : void 0;
|
|
4513
|
+
const testPath = payload && 'object' == typeof payload && 'testPath' in payload ? String(payload.testPath) : files[0].testPath;
|
|
4514
|
+
const file = files.find((candidate)=>candidate.testPath === normalize(testPath)) ?? files[0];
|
|
4512
4515
|
const response = await dispatchRouter.dispatch({
|
|
4513
4516
|
requestId: nextDispatchRequestId("runner"),
|
|
4514
4517
|
runToken: run.token,
|
|
@@ -4524,8 +4527,10 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4524
4527
|
if (response.stale) return;
|
|
4525
4528
|
if (response.error) throw new Error(response.error);
|
|
4526
4529
|
};
|
|
4527
|
-
const
|
|
4530
|
+
const runWorkerFiles = async (run, files, rawCoverage)=>{
|
|
4528
4531
|
if (run.cancelled || runLifecycle.isTokenStale(run.token)) return;
|
|
4532
|
+
const file = files[0];
|
|
4533
|
+
let activeFile = file;
|
|
4529
4534
|
const viewport = viewportByProject.get(file.projectName);
|
|
4530
4535
|
const browserContext = await browser.newContext({
|
|
4531
4536
|
providerOptions: browserLaunchOptions.providerOptions,
|
|
@@ -4537,9 +4542,31 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4537
4542
|
let coverageCollection;
|
|
4538
4543
|
let settled = false;
|
|
4539
4544
|
let resolveDone = null;
|
|
4540
|
-
let
|
|
4541
|
-
let
|
|
4542
|
-
|
|
4545
|
+
let workerCleanupTimer;
|
|
4546
|
+
let dispatchQueue = Promise.resolve();
|
|
4547
|
+
const completedFiles = new Set();
|
|
4548
|
+
const keepWorkerFixtures = false === projectIsolation.get(file.projectName);
|
|
4549
|
+
const fileCleanupStates = new Map();
|
|
4550
|
+
const handleWorkerCleanupTimeout = ()=>{
|
|
4551
|
+
if (settled) return;
|
|
4552
|
+
settled = true;
|
|
4553
|
+
(async ()=>{
|
|
4554
|
+
await closeContextSafely(browserContext);
|
|
4555
|
+
await handleFatal({
|
|
4556
|
+
message: `Worker fixture cleanup did not finish within ${FIXTURE_CLEANUP_TIMEOUT_MS}ms`
|
|
4557
|
+
});
|
|
4558
|
+
resolveDone?.();
|
|
4559
|
+
})();
|
|
4560
|
+
};
|
|
4561
|
+
const armWorkerCleanupDeadline = ()=>{
|
|
4562
|
+
if (workerCleanupTimer) clearTimeout(workerCleanupTimer);
|
|
4563
|
+
workerCleanupTimer = setTimeout(handleWorkerCleanupTimeout, FIXTURE_CLEANUP_TIMEOUT_MS);
|
|
4564
|
+
};
|
|
4565
|
+
const disarmWorkerCleanupDeadline = ()=>{
|
|
4566
|
+
if (!workerCleanupTimer) return;
|
|
4567
|
+
clearTimeout(workerCleanupTimer);
|
|
4568
|
+
workerCleanupTimer = void 0;
|
|
4569
|
+
};
|
|
4543
4570
|
const markDone = ()=>{
|
|
4544
4571
|
if (!settled) {
|
|
4545
4572
|
settled = true;
|
|
@@ -4560,6 +4587,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4560
4587
|
if (!coverageCollection) coverageCollection = (async ()=>{
|
|
4561
4588
|
const coverage = await v8Coverage.take(page, projectRootByName.get(file.projectName) ?? context.rootPath, file.projectName);
|
|
4562
4589
|
if (coverage) rawCoverage.push(coverage);
|
|
4590
|
+
if (keepWorkerFixtures) await v8Coverage.start(page);
|
|
4563
4591
|
})();
|
|
4564
4592
|
await coverageCollection;
|
|
4565
4593
|
};
|
|
@@ -4567,8 +4595,8 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4567
4595
|
page = await browserContext.newPage();
|
|
4568
4596
|
await v8Coverage?.start(page);
|
|
4569
4597
|
run.coverageCollectors.add(collectCoverage);
|
|
4570
|
-
page.on('crash', ()=>onPageDead(`Browser page crashed while running ${
|
|
4571
|
-
page.on('close', ()=>onPageDead(`Browser page closed unexpectedly while running ${
|
|
4598
|
+
page.on('crash', ()=>onPageDead(`Browser page crashed while running ${activeFile.testPath}.`));
|
|
4599
|
+
page.on('close', ()=>onPageDead(`Browser page closed unexpectedly while running ${activeFile.testPath}.`));
|
|
4572
4600
|
const session = sessionRegistry.register({
|
|
4573
4601
|
testFile: file.testPath,
|
|
4574
4602
|
projectName: file.projectName,
|
|
@@ -4578,73 +4606,108 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4578
4606
|
page
|
|
4579
4607
|
});
|
|
4580
4608
|
sessionId = session.id;
|
|
4581
|
-
const clearFileCleanupTimeout = ()=>{
|
|
4582
|
-
if (cleanupTimer) {
|
|
4583
|
-
clearTimeout(cleanupTimer);
|
|
4584
|
-
cleanupTimer = void 0;
|
|
4585
|
-
}
|
|
4586
|
-
};
|
|
4587
|
-
const finishFileCleanup = ()=>{
|
|
4588
|
-
fileCleanupFinished = true;
|
|
4589
|
-
clearFileCleanupTimeout();
|
|
4590
|
-
};
|
|
4591
4609
|
fileCleanupHandlers.set(session.id, {
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4610
|
+
workerStart: armWorkerCleanupDeadline,
|
|
4611
|
+
end: (payload)=>{
|
|
4612
|
+
const state = fileCleanupStates.get(payload.testPath);
|
|
4613
|
+
if (!state) return;
|
|
4614
|
+
state.finished = true;
|
|
4615
|
+
if (state.timer) {
|
|
4616
|
+
clearTimeout(state.timer);
|
|
4617
|
+
state.timer = void 0;
|
|
4618
|
+
}
|
|
4619
|
+
return collectCoverage().finally(()=>{
|
|
4620
|
+
coverageCollection = void 0;
|
|
4621
|
+
});
|
|
4622
|
+
},
|
|
4623
|
+
workerEnd: ()=>{
|
|
4624
|
+
disarmWorkerCleanupDeadline();
|
|
4625
|
+
return collectCoverage().finally(()=>{
|
|
4626
|
+
coverageCollection = void 0;
|
|
4627
|
+
});
|
|
4628
|
+
},
|
|
4629
|
+
start: async (payload)=>{
|
|
4630
|
+
const state = fileCleanupStates.get(payload.testPath) ?? {
|
|
4631
|
+
finished: false
|
|
4632
|
+
};
|
|
4633
|
+
if (state.finished) return;
|
|
4634
|
+
state.result = payload.result;
|
|
4635
|
+
if (state.timer) clearTimeout(state.timer);
|
|
4636
|
+
state.timer = setTimeout(()=>{
|
|
4598
4637
|
(async ()=>{
|
|
4599
4638
|
if (settled) return;
|
|
4600
4639
|
settled = true;
|
|
4601
4640
|
const message = `File fixture cleanup did not finish within ${FIXTURE_CLEANUP_TIMEOUT_MS}ms`;
|
|
4602
4641
|
try {
|
|
4603
|
-
|
|
4642
|
+
completedFiles.add(payload.testPath);
|
|
4604
4643
|
await handleTestFileComplete(createFileCleanupTimeoutResult({
|
|
4605
4644
|
message,
|
|
4606
4645
|
projectName: file.projectName,
|
|
4607
|
-
result:
|
|
4608
|
-
testPath:
|
|
4646
|
+
result: state.result,
|
|
4647
|
+
testPath: payload.testPath
|
|
4609
4648
|
}));
|
|
4649
|
+
for (const pendingFile of files)if (!completedFiles.has(pendingFile.testPath)) {
|
|
4650
|
+
completedFiles.add(pendingFile.testPath);
|
|
4651
|
+
await handleTestFileComplete(createFileCleanupTimeoutResult({
|
|
4652
|
+
message: `${message}; file was not run because the previous file cleanup timed out`,
|
|
4653
|
+
projectName: pendingFile.projectName,
|
|
4654
|
+
testPath: pendingFile.testPath
|
|
4655
|
+
}));
|
|
4656
|
+
}
|
|
4610
4657
|
} catch (error) {
|
|
4611
4658
|
const formatted = toError(error);
|
|
4659
|
+
await closeContextSafely(browserContext);
|
|
4612
4660
|
await handleFatal({
|
|
4613
4661
|
message: formatted.message,
|
|
4614
4662
|
stack: formatted.stack
|
|
4615
4663
|
});
|
|
4616
|
-
await collectRunCoverage(run);
|
|
4617
|
-
await cancelRun(run, false);
|
|
4618
4664
|
} finally{
|
|
4619
4665
|
resolveDone?.();
|
|
4620
4666
|
}
|
|
4621
4667
|
})();
|
|
4622
4668
|
}, FIXTURE_CLEANUP_TIMEOUT_MS);
|
|
4669
|
+
fileCleanupStates.set(payload.testPath, state);
|
|
4623
4670
|
}
|
|
4624
4671
|
});
|
|
4625
4672
|
await attachHeadlessRunnerTransport(page, {
|
|
4626
|
-
onDispatchMessage:
|
|
4627
|
-
const
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4673
|
+
onDispatchMessage: (envelope)=>{
|
|
4674
|
+
const queued = dispatchQueue.then(async ()=>{
|
|
4675
|
+
try {
|
|
4676
|
+
const message = envelope.message;
|
|
4677
|
+
if (settled) return;
|
|
4678
|
+
if ('file-start' === message.type) {
|
|
4679
|
+
const startedPath = normalize(message.payload.testPath);
|
|
4680
|
+
activeFile = files.find((candidate)=>candidate.testPath === startedPath) ?? activeFile;
|
|
4681
|
+
}
|
|
4682
|
+
if ('fatal' === message.type) await collectCoverage();
|
|
4683
|
+
await dispatchRunnerMessage(run, files, session.id, message);
|
|
4684
|
+
if ('file-complete' === message.type) {
|
|
4685
|
+
const completedPath = message.payload.testPath;
|
|
4686
|
+
completedFiles.add(completedPath);
|
|
4687
|
+
}
|
|
4688
|
+
if ('complete' !== message.type && ('file-complete' !== message.type || 1 !== files.length || keepWorkerFixtures)) {
|
|
4689
|
+
if ('fatal' === message.type) {
|
|
4690
|
+
markDone();
|
|
4691
|
+
await collectRunCoverage(run);
|
|
4692
|
+
await cancelRun(run, false);
|
|
4693
|
+
}
|
|
4694
|
+
} else {
|
|
4695
|
+
disarmWorkerCleanupDeadline();
|
|
4696
|
+
markDone();
|
|
4697
|
+
}
|
|
4698
|
+
} catch (error) {
|
|
4699
|
+
const formatted = toError(error);
|
|
4700
|
+
await handleFatal({
|
|
4701
|
+
message: formatted.message,
|
|
4702
|
+
stack: formatted.stack
|
|
4703
|
+
});
|
|
4634
4704
|
markDone();
|
|
4635
4705
|
await collectRunCoverage(run);
|
|
4636
4706
|
await cancelRun(run, false);
|
|
4637
4707
|
}
|
|
4638
|
-
}
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
message: formatted.message,
|
|
4642
|
-
stack: formatted.stack
|
|
4643
|
-
});
|
|
4644
|
-
markDone();
|
|
4645
|
-
await collectRunCoverage(run);
|
|
4646
|
-
await cancelRun(run, false);
|
|
4647
|
-
}
|
|
4708
|
+
});
|
|
4709
|
+
dispatchQueue = queued.catch(()=>{});
|
|
4710
|
+
return queued;
|
|
4648
4711
|
},
|
|
4649
4712
|
onDispatchRpc: async (request)=>dispatchRouter.dispatch({
|
|
4650
4713
|
...request,
|
|
@@ -4663,6 +4726,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4663
4726
|
updateSnapshot: context.snapshotManager.options.updateSnapshot
|
|
4664
4727
|
},
|
|
4665
4728
|
testFile: file.testPath,
|
|
4729
|
+
testFiles: files.map((item)=>item.testPath),
|
|
4666
4730
|
runId: `${run.token}:${session.id}`
|
|
4667
4731
|
};
|
|
4668
4732
|
const serializedOptions = serializeForInlineScript(inlineOptions);
|
|
@@ -4703,7 +4767,8 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4703
4767
|
await cancelRun(run, false);
|
|
4704
4768
|
}
|
|
4705
4769
|
} finally{
|
|
4706
|
-
if (
|
|
4770
|
+
for (const state of fileCleanupStates.values())if (state.timer) clearTimeout(state.timer);
|
|
4771
|
+
if (workerCleanupTimer) clearTimeout(workerCleanupTimer);
|
|
4707
4772
|
if (sessionId) fileCleanupHandlers.delete(sessionId);
|
|
4708
4773
|
run.coverageCollectors.delete(collectCoverage);
|
|
4709
4774
|
const abandoned = run.cancelled || runLifecycle.isTokenStale(run.token);
|
|
@@ -4742,6 +4807,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4742
4807
|
];
|
|
4743
4808
|
const concurrency = getHeadlessConcurrency(context, queue.length);
|
|
4744
4809
|
const bail = context.normalizedConfig.bail;
|
|
4810
|
+
const nonIsolatedBatchSize = Math.max(1, bail ? 1 : Math.ceil(files.length / Math.max(concurrency, 1)));
|
|
4745
4811
|
const worker = async ()=>{
|
|
4746
4812
|
while(queue.length > 0 && !run.cancelled && runLifecycle.isTokenActive(run.token)){
|
|
4747
4813
|
if (bail && context.stateManager.getCountOfFailedTests() >= bail) {
|
|
@@ -4754,7 +4820,11 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4754
4820
|
}
|
|
4755
4821
|
const next = queue.shift();
|
|
4756
4822
|
if (!next) return;
|
|
4757
|
-
|
|
4823
|
+
const batch = [
|
|
4824
|
+
next
|
|
4825
|
+
];
|
|
4826
|
+
if (false === projectIsolation.get(next.projectName)) while(batch.length < nonIsolatedBatchSize && queue[0]?.projectName === next.projectName)batch.push(queue.shift());
|
|
4827
|
+
await runWorkerFiles(run, batch, rawCoverage);
|
|
4758
4828
|
}
|
|
4759
4829
|
};
|
|
4760
4830
|
run.done = Promise.all(Array.from({
|
|
@@ -5189,6 +5259,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
5189
5259
|
name: project.name,
|
|
5190
5260
|
environmentName: project.environmentName,
|
|
5191
5261
|
projectRoot: normalize(project.rootPath),
|
|
5262
|
+
hasSetupFiles: (project.normalizedConfig.setupFiles?.length ?? 0) > 0,
|
|
5192
5263
|
runtimeConfig: serializableConfig(projectRuntimeConfig(project, {
|
|
5193
5264
|
envMode: 'static',
|
|
5194
5265
|
envOverlay: env
|
|
@@ -5584,6 +5655,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
5584
5655
|
name: project.name,
|
|
5585
5656
|
environmentName: project.environmentName,
|
|
5586
5657
|
projectRoot: normalize(project.rootPath),
|
|
5658
|
+
hasSetupFiles: (project.normalizedConfig.setupFiles?.length ?? 0) > 0,
|
|
5587
5659
|
runtimeConfig: serializableConfig(projectRuntimeConfig(project, {
|
|
5588
5660
|
envMode: 'static',
|
|
5589
5661
|
envOverlay: options?.env
|
package/dist/protocol.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type BrowserProjectRuntime = {
|
|
|
23
23
|
name: string;
|
|
24
24
|
environmentName: string;
|
|
25
25
|
projectRoot: string;
|
|
26
|
+
/** Setup modules are re-evaluated per file, so they cannot share a worker page. */ hasSetupFiles: boolean;
|
|
26
27
|
runtimeConfig: SerializedRuntimeConfig;
|
|
27
28
|
viewport?: BrowserViewport;
|
|
28
29
|
};
|
|
@@ -33,7 +34,7 @@ export type BrowserProjectRuntime = {
|
|
|
33
34
|
testPath: string;
|
|
34
35
|
projectName: string;
|
|
35
36
|
};
|
|
36
|
-
export type FileCleanupDispatchMethod = 'start' | 'end';
|
|
37
|
+
export type FileCleanupDispatchMethod = 'start' | 'end' | 'worker-start' | 'worker-end';
|
|
37
38
|
export type FileCleanupDispatchPayload = {
|
|
38
39
|
projectName: string;
|
|
39
40
|
result?: TestFileResult;
|
|
@@ -82,6 +83,7 @@ export type BrowserHostConfig = {
|
|
|
82
83
|
updateSnapshot: SnapshotUpdateState;
|
|
83
84
|
};
|
|
84
85
|
testFile?: string;
|
|
86
|
+
/** Test files assigned to one browser worker session. */ testFiles?: string[];
|
|
85
87
|
/**
|
|
86
88
|
* The run identity this document executes under — the runner's SOLE identity
|
|
87
89
|
* source, adopted once at boot and stamped on every outbound message. Headed:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rstest/browser",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.9",
|
|
4
4
|
"description": "Browser mode support for Rstest testing framework.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rstest",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"ws": "^8.21.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@rslib/core": "1.0.0-beta.
|
|
51
|
+
"@rslib/core": "1.0.0-beta.3",
|
|
52
52
|
"@types/convert-source-map": "^2.0.3",
|
|
53
53
|
"@types/picomatch": "^4.0.3",
|
|
54
54
|
"@types/ws": "^8.18.1",
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"birpc": "^4.1.0",
|
|
57
57
|
"picomatch": "^4.0.5",
|
|
58
58
|
"playwright": "^1.62.1",
|
|
59
|
+
"@rstest/tsconfig": "0.0.1",
|
|
59
60
|
"@rstest/browser-ui": "0.0.0",
|
|
60
|
-
"@rstest/core": "0.11.
|
|
61
|
-
"@rstest/tsconfig": "0.0.1"
|
|
61
|
+
"@rstest/core": "0.11.9"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"playwright": "^1.49.1",
|
|
65
|
-
"@rstest/core": "0.11.
|
|
65
|
+
"@rstest/core": "0.11.9"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"playwright": {
|