@rstest/browser 0.11.6 → 0.11.8
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/browser-container/container-static/css/index.43daaf6c93.css +1 -0
- package/dist/browser-container/container-static/js/260.3739fe815c.js +194 -0
- package/dist/browser-container/container-static/js/index.c0f87bfb3e.js +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browserRsbuild.d.ts +24 -4
- package/dist/browserV8Coverage.d.ts +15 -0
- package/dist/client/240~1.js +150 -0
- package/dist/{augmentExpect.d.ts → client/client/augmentExpect.d.ts} +1 -1
- package/dist/client/{browserRpc.d.ts → client/browserRpc.d.ts} +1 -1
- package/dist/client/{dispatchTransport.d.ts → client/dispatchTransport.d.ts} +8 -4
- package/dist/client/client/index.d.ts +2 -0
- package/dist/client/client/runIdentity.d.ts +12 -0
- package/dist/client/client/runner.d.ts +4 -0
- package/dist/client/client/snapshot.d.ts +27 -0
- package/dist/client/client/sourceMapSupport.d.ts +36 -0
- package/dist/{browser.js → client/index.js} +3 -102
- package/dist/client/protocol.d.ts +225 -0
- package/dist/client/rpcProtocol.d.ts +138 -0
- package/dist/client/runner.js +675 -0
- package/dist/client/sourceMap/sourceMapLoader.d.ts +25 -0
- package/dist/containerRpc.d.ts +27 -19
- package/dist/headedRunRegistry.d.ts +51 -0
- package/dist/headedScheduler.d.ts +10 -10
- package/dist/headlessScheduler.d.ts +5 -4
- package/dist/headlessTransport.d.ts +4 -2
- package/dist/hostPayloads.d.ts +1 -7
- package/dist/index.js +840 -330
- package/dist/protocol.d.ts +48 -8
- package/dist/providers/index.d.ts +21 -0
- package/dist/providers/playwright/v8Coverage.d.ts +2 -0
- package/dist/rpcProtocol.d.ts +1 -2
- package/dist/schedulerSeam.d.ts +5 -0
- package/dist/sourceMap/sourceMapLoader.d.ts +12 -1
- package/dist/watchRerunPlanner.d.ts +20 -9
- package/dist/watchSignals.d.ts +8 -1
- package/package.json +11 -11
- package/dist/33.js +0 -7
- package/dist/browser-container/container-static/css/index.0521ff95b1.css +0 -1
- package/dist/browser-container/container-static/js/683.4f821ce8f1.js +0 -194
- package/dist/browser-container/container-static/js/index.568aa7cb35.js +0 -1
- package/dist/browser.d.ts +0 -2
- package/src/augmentExpect.ts +0 -62
- package/src/browser.ts +0 -3
- package/src/browserExecutor.ts +0 -222
- package/src/browserRpcRegistry.ts +0 -57
- package/src/browserRsbuild.ts +0 -1927
- package/src/client/api.ts +0 -213
- package/src/client/browserRpc.ts +0 -86
- package/src/client/dispatchTransport.ts +0 -240
- package/src/client/entry.ts +0 -807
- package/src/client/locator.ts +0 -448
- package/src/client/snapshot.ts +0 -113
- package/src/client/sourceMapSupport.ts +0 -159
- package/src/concurrency.ts +0 -65
- package/src/configValidation.ts +0 -263
- package/src/containerRpc.ts +0 -206
- package/src/dispatchCapabilities.ts +0 -177
- package/src/dispatchRouter.ts +0 -82
- package/src/env.d.ts +0 -54
- package/src/headedScheduler.ts +0 -664
- package/src/headedSerialTaskQueue.ts +0 -23
- package/src/headlessScheduler.ts +0 -566
- package/src/headlessTransport.ts +0 -28
- package/src/hostController.ts +0 -1365
- package/src/hostPayloads.ts +0 -62
- package/src/index.ts +0 -51
- package/src/manifest.d.ts +0 -41
- package/src/protocol.ts +0 -225
- package/src/providers/index.ts +0 -126
- package/src/providers/playwright/compileLocator.ts +0 -130
- package/src/providers/playwright/dispatchBrowserRpc.ts +0 -372
- package/src/providers/playwright/expectUtils.ts +0 -57
- package/src/providers/playwright/implementation.ts +0 -35
- package/src/providers/playwright/index.ts +0 -1
- package/src/providers/playwright/runtime.ts +0 -159
- package/src/providers/playwright/textMatcher.ts +0 -10
- package/src/rpcProtocol.ts +0 -218
- package/src/runSession.ts +0 -110
- package/src/schedulerSeam.ts +0 -49
- package/src/sessionRegistry.ts +0 -89
- package/src/sourceMap/sourceMapLoader.ts +0 -96
- package/src/viewportPresets.ts +0 -69
- package/src/watchRerunPlanner.ts +0 -88
- package/src/watchRuntime.ts +0 -83
- package/src/watchSignals.ts +0 -93
- /package/dist/{rslib-runtime.js → 612~0.js} +0 -0
- /package/dist/client/{api.d.ts → client/api.d.ts} +0 -0
- /package/dist/client/{locator.d.ts → client/locator.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { DEFAULT_TEST_TIMEOUT, FATAL_SIGNALS, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, applyWatchInvalidation, applyWebMockRspackConfig, browserIgnoredRuntimeConfigKeys, buildBrowserCoverageMap, color, createCoverageProvider, createRunnerEventSink, createSilentConsoleController, getNumCpus as browser_getNumCpus, getPrettyConsoleName, getSetupFiles, getTestEntries, hasUserRstestConfigPlugins,
|
|
1
|
+
import { DEFAULT_TEST_TIMEOUT, FATAL_SIGNALS, FIXTURE_CLEANUP_TIMEOUT_MS, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, applyWatchInvalidation, applyWebMockRspackConfig, browserIgnoredRuntimeConfigKeys, buildBrowserCoverageMap, color, createCoverageProvider, createFileCleanupTimeoutResult, createRunnerEventSink, createSilentConsoleController, getNumCpus as browser_getNumCpus, getPrettyConsoleName, getSetupFiles, getTestEntries, hasUserRstestConfigPlugins, initModifyRstestConfigHooks, isDebug, isTTY, loadCoverageProvider, logWatchReadyMessage, logger, pluginMockRuntime, projectRuntimeConfig, resolveProjectBuildCache, resolveShardedEntries, resolveSnapshotPathDefault, resolveWorkerCount, rsbuild, serializableConfig } from "@rstest/core/internal/browser";
|
|
2
2
|
import promises from "node:fs/promises";
|
|
3
|
-
import { dirname, join, normalize, relative, resolve as external_pathe_resolve } from "pathe";
|
|
3
|
+
import { dirname, isAbsolute as external_pathe_isAbsolute, join, normalize, relative, resolve as external_pathe_resolve } from "pathe";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { isDeepStrictEqual } from "node:util";
|
|
7
7
|
import open_editor from "open-editor";
|
|
8
8
|
import sirv from "sirv";
|
|
9
9
|
import { WebSocketServer } from "ws";
|
|
10
|
+
import { AnyMap, TraceMap, encodedMap } from "@jridgewell/trace-mapping";
|
|
10
11
|
import convert_source_map from "convert-source-map";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { __webpack_require__ } from "./612~0.js";
|
|
13
14
|
__webpack_require__.add({
|
|
14
15
|
"../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/index.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
15
16
|
const pico = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/lib/picomatch.js");
|
|
@@ -1702,6 +1703,12 @@ class HostDispatchRouter {
|
|
|
1702
1703
|
}
|
|
1703
1704
|
}
|
|
1704
1705
|
}
|
|
1706
|
+
const DISPATCH_MESSAGE_TYPE = '__rstest_dispatch__';
|
|
1707
|
+
const DISPATCH_RPC_BRIDGE_NAME = '__rstest_dispatch_rpc__';
|
|
1708
|
+
const DISPATCH_NAMESPACE_BROWSER = 'browser';
|
|
1709
|
+
const DISPATCH_NAMESPACE_FILE_CLEANUP = 'file-cleanup';
|
|
1710
|
+
const DISPATCH_NAMESPACE_SNAPSHOT = 'snapshot';
|
|
1711
|
+
const RSTEST_BROWSER_CACHE_CLEANERS_KEY = '@rstest/browser/cache-cleaners';
|
|
1705
1712
|
const snapshotRequestBuilders = {
|
|
1706
1713
|
resolveSnapshotPath: (id, args)=>({
|
|
1707
1714
|
id,
|
|
@@ -1842,11 +1849,6 @@ const ignoredKeyWarnings = {
|
|
|
1842
1849
|
message: ()=>"Ignoring isolate: false in browser mode: each test file still runs in a fresh context.",
|
|
1843
1850
|
browserOnly: true
|
|
1844
1851
|
},
|
|
1845
|
-
federation: {
|
|
1846
|
-
isNonDefault: (config)=>true === config.federation,
|
|
1847
|
-
message: ()=>'Ignoring federation in browser mode: it only applies to the Node runner.',
|
|
1848
|
-
browserOnly: true
|
|
1849
|
-
},
|
|
1850
1852
|
detectAsyncLeaks: {
|
|
1851
1853
|
isNonDefault: (config)=>true === config.detectAsyncLeaks,
|
|
1852
1854
|
message: ()=>"Ignoring detectAsyncLeaks in browser mode: it relies on node async_hooks."
|
|
@@ -1897,8 +1899,9 @@ const reportUnsupportedBrowserOptions = (context)=>{
|
|
|
1897
1899
|
const globalConfig = context.normalizedConfig;
|
|
1898
1900
|
const { coverage } = globalConfig;
|
|
1899
1901
|
if ('list' !== context.command && coverage.enabled && 'v8' === coverage.provider) {
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
+
const unsupportedProject = browserProjects.find((project)=>'chromium' !== project.normalizedConfig.browser.browser);
|
|
1903
|
+
if (unsupportedProject) if (isBrowserOnlyRun) throw new Error(`Coverage provider 'v8' in browser mode requires Chromium. Project ${JSON.stringify(unsupportedProject.name)} uses ${JSON.stringify(unsupportedProject.normalizedConfig.browser.browser)}. Use Chromium or coverage.provider: 'istanbul'.`);
|
|
1904
|
+
else logger.warn(color.yellow(`Coverage provider 'v8' requires Chromium for browser project ${JSON.stringify(unsupportedProject.name)}. Its files will not produce coverage; node projects will still use v8.`));
|
|
1902
1905
|
}
|
|
1903
1906
|
const warnings = new Set();
|
|
1904
1907
|
if ('forks' !== globalConfig.pool.type) warnings.add(`Ignoring pool.type '${globalConfig.pool.type}' in browser mode.`);
|
|
@@ -2350,6 +2353,17 @@ async function launchPlaywrightBrowser({ browserName, headless, providerOptions
|
|
|
2350
2353
|
browser: wrappedBrowser
|
|
2351
2354
|
};
|
|
2352
2355
|
}
|
|
2356
|
+
const asCoveragePage = (page)=>page;
|
|
2357
|
+
const createPlaywrightV8CoverageCollector = ()=>({
|
|
2358
|
+
async start (page) {
|
|
2359
|
+
await asCoveragePage(page).coverage.startJSCoverage({
|
|
2360
|
+
resetOnNavigation: false
|
|
2361
|
+
});
|
|
2362
|
+
},
|
|
2363
|
+
async take (page) {
|
|
2364
|
+
return asCoveragePage(page).coverage.stopJSCoverage();
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2353
2367
|
const playwrightProviderImplementation = {
|
|
2354
2368
|
name: 'playwright',
|
|
2355
2369
|
async launchRuntime ({ browserName, headless, providerOptions }) {
|
|
@@ -2359,6 +2373,9 @@ const playwrightProviderImplementation = {
|
|
|
2359
2373
|
providerOptions
|
|
2360
2374
|
});
|
|
2361
2375
|
},
|
|
2376
|
+
createV8CoverageCollector ({ browserName }) {
|
|
2377
|
+
return 'chromium' === browserName ? createPlaywrightV8CoverageCollector() : null;
|
|
2378
|
+
},
|
|
2362
2379
|
async dispatchRpc ({ containerPage, runnerPage, request, timeoutFallbackMs }) {
|
|
2363
2380
|
return dispatchPlaywrightBrowserRpc({
|
|
2364
2381
|
containerPage: containerPage,
|
|
@@ -2376,15 +2393,18 @@ function getBrowserProviderImplementation(provider) {
|
|
|
2376
2393
|
if (!implementation) throw new Error(`Unsupported browser provider: ${String(provider)}`);
|
|
2377
2394
|
return implementation;
|
|
2378
2395
|
}
|
|
2379
|
-
const collectDeletedTestPaths = (previous, current)=>{
|
|
2380
|
-
const currentPathSet = new Set(current.map((file)=>file.testPath));
|
|
2381
|
-
return previous.map((file)=>file.testPath).filter((testPath)=>!currentPathSet.has(testPath));
|
|
2382
|
-
};
|
|
2383
2396
|
const serializeTestFiles = (files)=>JSON.stringify(files.map((f)=>`${f.projectName}:${f.testPath}`).sort());
|
|
2384
2397
|
const normalizeTestFiles = (files)=>files.map((file)=>({
|
|
2385
2398
|
...file,
|
|
2386
2399
|
testPath: normalize(file.testPath)
|
|
2387
2400
|
}));
|
|
2401
|
+
const collectTestPaths = (files)=>[
|
|
2402
|
+
...new Set(files.map((file)=>file.testPath))
|
|
2403
|
+
];
|
|
2404
|
+
const collectDeletedTestPaths = (previous, current)=>{
|
|
2405
|
+
const currentPathSet = new Set(current.map((file)=>file.testPath));
|
|
2406
|
+
return previous.map((file)=>file.testPath).filter((testPath)=>!currentPathSet.has(testPath));
|
|
2407
|
+
};
|
|
2388
2408
|
const collectWatchTestFiles = (projectEntries)=>projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
2389
2409
|
testPath: normalize(testPath),
|
|
2390
2410
|
projectName: entry.project.name
|
|
@@ -2392,23 +2412,88 @@ const collectWatchTestFiles = (projectEntries)=>projectEntries.flatMap((entry)=>
|
|
|
2392
2412
|
const planWatchRerun = ({ projectEntries, previousTestFiles, affectedTestFiles })=>{
|
|
2393
2413
|
const currentTestFiles = collectWatchTestFiles(projectEntries);
|
|
2394
2414
|
const normalizedPrevious = normalizeTestFiles(previousTestFiles);
|
|
2395
|
-
const
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2415
|
+
const fileSetChanged = serializeTestFiles(currentTestFiles) !== serializeTestFiles(normalizedPrevious);
|
|
2416
|
+
if (fileSetChanged) {
|
|
2417
|
+
const fileSetUpdate = {
|
|
2418
|
+
currentTestFiles,
|
|
2419
|
+
deletedTestPaths: collectDeletedTestPaths(normalizedPrevious, currentTestFiles)
|
|
2420
|
+
};
|
|
2421
|
+
if (0 === currentTestFiles.length) return {
|
|
2422
|
+
fileSetUpdate,
|
|
2423
|
+
decision: {
|
|
2424
|
+
kind: 'empty',
|
|
2425
|
+
message: 'No browser test files remain after update.\n'
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
return {
|
|
2429
|
+
fileSetUpdate,
|
|
2430
|
+
decision: {
|
|
2431
|
+
kind: 'rerun',
|
|
2432
|
+
testPaths: collectTestPaths(currentTestFiles),
|
|
2433
|
+
message: `Test file set changed, re-running ${currentTestFiles.length} file(s)...\n`
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
const affectedPathSet = new Set(affectedTestFiles.map((testFile)=>normalize(testFile)));
|
|
2438
|
+
const matchedAffectedFiles = currentTestFiles.filter(({ testPath })=>affectedPathSet.has(testPath));
|
|
2439
|
+
if (0 === matchedAffectedFiles.length) return {
|
|
2440
|
+
decision: {
|
|
2441
|
+
kind: 'idle',
|
|
2442
|
+
message: 'No affected browser test files detected, skipping re-run.\n'
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2399
2445
|
return {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2446
|
+
decision: {
|
|
2447
|
+
kind: 'rerun',
|
|
2448
|
+
testPaths: collectTestPaths(matchedAffectedFiles),
|
|
2449
|
+
message: `Re-running ${matchedAffectedFiles.length} affected test file(s)...\n`
|
|
2450
|
+
}
|
|
2404
2451
|
};
|
|
2405
2452
|
};
|
|
2453
|
+
const commitWatchFileSetUpdate = (update, watchState, pruneDeletedTestPaths)=>{
|
|
2454
|
+
if (!update) return;
|
|
2455
|
+
if (update.deletedTestPaths.length > 0) pruneDeletedTestPaths(update.deletedTestPaths);
|
|
2456
|
+
watchState.lastTestFiles = update.currentTestFiles;
|
|
2457
|
+
};
|
|
2406
2458
|
const picomatch = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.5/node_modules/picomatch/index.js");
|
|
2407
2459
|
var picomatch_default = /*#__PURE__*/ __webpack_require__.n(picomatch);
|
|
2408
2460
|
const { createRsbuild: createRsbuild, rspack: rspack } = rsbuild;
|
|
2409
2461
|
const browserRsbuild_dirname = dirname(fileURLToPath(import.meta.url));
|
|
2410
2462
|
const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
|
|
2411
2463
|
const serializeForInlineScript = (value)=>JSON.stringify(value).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
2464
|
+
class RstestBrowserRuntimePlugin {
|
|
2465
|
+
apply(compiler) {
|
|
2466
|
+
const { RuntimeModule } = compiler.webpack;
|
|
2467
|
+
class BrowserRuntimeModule extends RuntimeModule {
|
|
2468
|
+
constructor(){
|
|
2469
|
+
super('rstest_browser_runtime');
|
|
2470
|
+
}
|
|
2471
|
+
generate() {
|
|
2472
|
+
return `
|
|
2473
|
+
var __rstest_browser_test_entry_ids__ = Object.create(null);
|
|
2474
|
+
|
|
2475
|
+
__webpack_require__.rstest_register_browser_test_entry = (path, id) => {
|
|
2476
|
+
__rstest_browser_test_entry_ids__[path] = id;
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
function __rstest_clean_browser_test_entry__(testEntryPath) {
|
|
2480
|
+
var testEntryId = __rstest_browser_test_entry_ids__[testEntryPath];
|
|
2481
|
+
if (testEntryId !== undefined) {
|
|
2482
|
+
delete __webpack_module_cache__[testEntryId];
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
(globalThis[${JSON.stringify(RSTEST_BROWSER_CACHE_CLEANERS_KEY)}] ??= new Set()).add(__rstest_clean_browser_test_entry__);
|
|
2487
|
+
`;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
compiler.hooks.thisCompilation.tap('RstestBrowserRuntimePlugin', (compilation)=>{
|
|
2491
|
+
compilation.hooks.additionalTreeRuntimeRequirements.tap('RstestAddBrowserRuntimePlugin', (chunk)=>{
|
|
2492
|
+
compilation.addRuntimeModule(chunk, new BrowserRuntimeModule());
|
|
2493
|
+
});
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2412
2497
|
const getBrowserProviderOptions = (project)=>{
|
|
2413
2498
|
const browserConfig = project.normalizedConfig.browser;
|
|
2414
2499
|
return browserConfig.providerOptions ?? {};
|
|
@@ -2416,6 +2501,7 @@ const getBrowserProviderOptions = (project)=>{
|
|
|
2416
2501
|
const createBrowserWatchState = ()=>({
|
|
2417
2502
|
lastTestFiles: [],
|
|
2418
2503
|
hooksEnabled: false,
|
|
2504
|
+
headedFileSetVersion: 1,
|
|
2419
2505
|
invalidation: new Map(),
|
|
2420
2506
|
pendingAffectedTestFiles: new Map(),
|
|
2421
2507
|
compileStartTimes: new Map(),
|
|
@@ -2688,13 +2774,13 @@ const browserRsbuild_collectProjectEntries = async (context, browserProjects = g
|
|
|
2688
2774
|
testFiles: Object.values(tests)
|
|
2689
2775
|
};
|
|
2690
2776
|
}));
|
|
2691
|
-
const
|
|
2777
|
+
const resolveBrowserDistFile = (relativePath)=>{
|
|
2692
2778
|
const candidates = [
|
|
2693
|
-
external_pathe_resolve(browserRsbuild_dirname,
|
|
2694
|
-
external_pathe_resolve(browserRsbuild_dirname, relativePath)
|
|
2779
|
+
external_pathe_resolve(browserRsbuild_dirname, relativePath),
|
|
2780
|
+
external_pathe_resolve(browserRsbuild_dirname, '../dist', relativePath)
|
|
2695
2781
|
];
|
|
2696
2782
|
for (const candidate of candidates)if (existsSync(candidate)) return candidate;
|
|
2697
|
-
throw new Error(`Unable to resolve browser
|
|
2783
|
+
throw new Error(`Unable to resolve browser dist file: ${relativePath}`);
|
|
2698
2784
|
};
|
|
2699
2785
|
const resolveContainerDist = ()=>{
|
|
2700
2786
|
const distPath = external_pathe_resolve(browserRsbuild_dirname, 'browser-container');
|
|
@@ -2839,7 +2925,7 @@ const destroyBrowserRuntime = async (runtime)=>{
|
|
|
2839
2925
|
force: true
|
|
2840
2926
|
}).catch(()=>{});
|
|
2841
2927
|
};
|
|
2842
|
-
const createBrowserRuntime = async ({ context, projectEntries: initialProjectEntries, browserProjects, shardedEntries, freezeShardedEntries, tempDir, isWatchMode,
|
|
2928
|
+
const createBrowserRuntime = async ({ context, projectEntries: initialProjectEntries, browserProjects, shardedEntries, freezeShardedEntries, tempDir, isWatchMode, containerDistPath, containerDevServer, forceHeadless, skipProviderLaunch, appliedModifyRstestConfigEnvironments })=>{
|
|
2843
2929
|
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
2844
2930
|
let injectedContainerHtml = null;
|
|
2845
2931
|
let serializedOptions = 'null';
|
|
@@ -2903,7 +2989,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
2903
2989
|
};
|
|
2904
2990
|
const browserRuntimePath = fileURLToPath(import.meta.resolve('@rstest/core/internal/browser-runtime'));
|
|
2905
2991
|
const staticRstestAliases = {
|
|
2906
|
-
'@rstest/browser':
|
|
2992
|
+
'@rstest/browser': resolveBrowserDistFile('client/index.js'),
|
|
2907
2993
|
'@rstest/core/internal/browser-runtime': browserRuntimePath
|
|
2908
2994
|
};
|
|
2909
2995
|
const rstestEnvDefine = `globalThis[Symbol.for(${JSON.stringify(RSTEST_ENV_SYMBOL_KEY)})]`;
|
|
@@ -2991,7 +3077,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
2991
3077
|
modules: virtualManifestModules
|
|
2992
3078
|
});
|
|
2993
3079
|
const rstestInternalAliases = {
|
|
2994
|
-
|
|
3080
|
+
__rstest_virtual_browser_manifest__: manifestPath,
|
|
2995
3081
|
...staticRstestAliases
|
|
2996
3082
|
};
|
|
2997
3083
|
const isHeadless = forceHeadless || project.normalizedConfig.browser.headless;
|
|
@@ -3029,6 +3115,17 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3029
3115
|
{
|
|
3030
3116
|
name: 'rstest:browser-user-config',
|
|
3031
3117
|
setup (api) {
|
|
3118
|
+
if ('list' === context.command) {
|
|
3119
|
+
let testEntryPaths;
|
|
3120
|
+
const getTestEntryPaths = ()=>testEntryPaths ??= new Set(getProjectEntry(project)?.testFiles.map(normalize) ?? []);
|
|
3121
|
+
api.transform({
|
|
3122
|
+
test: (resourcePath)=>getTestEntryPaths().has(normalize(resourcePath))
|
|
3123
|
+
}, ({ code, resourcePath })=>({
|
|
3124
|
+
code: `${code}\nif (__webpack_require__.rstest_register_browser_test_entry && __webpack_module__.id != null) {
|
|
3125
|
+
__webpack_require__.rstest_register_browser_test_entry(${JSON.stringify(normalize(resourcePath))}, __webpack_module__.id);
|
|
3126
|
+
}`
|
|
3127
|
+
}));
|
|
3128
|
+
}
|
|
3032
3129
|
api.expose?.('rstest:browser', {
|
|
3033
3130
|
registerDispatchHandler: (namespace, handler)=>{
|
|
3034
3131
|
dispatchHandlers.set(namespace, handler);
|
|
@@ -3056,8 +3153,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3056
3153
|
source: {
|
|
3057
3154
|
define: {
|
|
3058
3155
|
'process.env': rstestEnvDefine,
|
|
3059
|
-
'import.meta.env': rstestEnvDefine
|
|
3060
|
-
'import.meta.rstest': importMetaRstestDefine('web')
|
|
3156
|
+
'import.meta.env': rstestEnvDefine
|
|
3061
3157
|
}
|
|
3062
3158
|
},
|
|
3063
3159
|
output: {
|
|
@@ -3085,6 +3181,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3085
3181
|
});
|
|
3086
3182
|
rspackConfig.lazyCompilation = enableHmr ? createBrowserLazyCompilationConfig(setupFiles) : false;
|
|
3087
3183
|
rspackConfig.plugins = rspackConfig.plugins || [];
|
|
3184
|
+
rspackConfig.plugins.push(new RstestBrowserRuntimePlugin());
|
|
3088
3185
|
rspackConfig.plugins.push(virtualManifestPlugin);
|
|
3089
3186
|
applyDefaultWatchOptions(rspackConfig, isWatchMode);
|
|
3090
3187
|
const browserRuntimeDir = dirname(browserRuntimePath);
|
|
@@ -3101,7 +3198,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3101
3198
|
});
|
|
3102
3199
|
merged.source = merged.source || {};
|
|
3103
3200
|
merged.source.entry = {
|
|
3104
|
-
runner:
|
|
3201
|
+
runner: resolveBrowserDistFile('client/runner.js')
|
|
3105
3202
|
};
|
|
3106
3203
|
return merged;
|
|
3107
3204
|
},
|
|
@@ -3110,7 +3207,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3110
3207
|
}
|
|
3111
3208
|
}
|
|
3112
3209
|
]);
|
|
3113
|
-
if (isWatchMode
|
|
3210
|
+
if (isWatchMode) rsbuildInstance.addPlugins([
|
|
3114
3211
|
{
|
|
3115
3212
|
name: 'rstest:browser-watch',
|
|
3116
3213
|
setup (api) {
|
|
@@ -3155,7 +3252,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3155
3252
|
}
|
|
3156
3253
|
}
|
|
3157
3254
|
if (!watchState.hooksEnabled) return;
|
|
3158
|
-
await
|
|
3255
|
+
await watchState.triggerRerun?.();
|
|
3159
3256
|
});
|
|
3160
3257
|
}
|
|
3161
3258
|
}
|
|
@@ -3306,8 +3403,182 @@ async function resolveProjectEntries(context, shardedEntries, browserProjects) {
|
|
|
3306
3403
|
}
|
|
3307
3404
|
return browserRsbuild_collectProjectEntries(context, browserProjects);
|
|
3308
3405
|
}
|
|
3309
|
-
const
|
|
3310
|
-
const
|
|
3406
|
+
const flattenSourceMap = (sourceMap, sourceMapUrl)=>encodedMap(new AnyMap(sourceMap, sourceMapUrl));
|
|
3407
|
+
const normalizeJavaScriptUrl = (value, options)=>{
|
|
3408
|
+
try {
|
|
3409
|
+
const url = options?.origin ? new URL(value, options.origin) : new URL(value);
|
|
3410
|
+
if ('http:' !== url.protocol && 'https:' !== url.protocol) return null;
|
|
3411
|
+
url.hash = '';
|
|
3412
|
+
return url.toString();
|
|
3413
|
+
} catch {
|
|
3414
|
+
return null;
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
const resolveInlineSourceMap = (code, sourceMapUrl)=>{
|
|
3418
|
+
try {
|
|
3419
|
+
const converter = convert_source_map.fromSource(code);
|
|
3420
|
+
return converter ? flattenSourceMap(converter.toObject(), sourceMapUrl) : null;
|
|
3421
|
+
} catch {
|
|
3422
|
+
return null;
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
const loadSourceMapForSource = async ({ jsUrl, signal, source, fetcher = fetch })=>{
|
|
3426
|
+
const normalizedUrl = normalizeJavaScriptUrl(jsUrl);
|
|
3427
|
+
if (!normalizedUrl) return null;
|
|
3428
|
+
try {
|
|
3429
|
+
const matches = [
|
|
3430
|
+
...source.matchAll(convert_source_map.mapFileCommentRegex)
|
|
3431
|
+
];
|
|
3432
|
+
const finalMatch = matches.at(-1);
|
|
3433
|
+
const sourceMapUrl = finalMatch?.[1] ?? finalMatch?.[2]?.trim();
|
|
3434
|
+
if (!sourceMapUrl || sourceMapUrl.startsWith('data:')) return null;
|
|
3435
|
+
const resolvedSourceMapUrl = new URL(sourceMapUrl, normalizedUrl);
|
|
3436
|
+
if ('http:' !== resolvedSourceMapUrl.protocol && 'https:' !== resolvedSourceMapUrl.protocol) return null;
|
|
3437
|
+
const mapResponse = await fetcher(resolvedSourceMapUrl.href, signal ? {
|
|
3438
|
+
signal
|
|
3439
|
+
} : void 0);
|
|
3440
|
+
return mapResponse.ok ? {
|
|
3441
|
+
sourceMap: flattenSourceMap(await mapResponse.json(), resolvedSourceMapUrl.href),
|
|
3442
|
+
sourceMapUrl: resolvedSourceMapUrl.href
|
|
3443
|
+
} : null;
|
|
3444
|
+
} catch {
|
|
3445
|
+
return null;
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
const fetchSourceMap = async (jsUrl, fetcher)=>{
|
|
3449
|
+
const jsResponse = await fetcher(jsUrl);
|
|
3450
|
+
if (!jsResponse.ok) return null;
|
|
3451
|
+
const code = await jsResponse.text();
|
|
3452
|
+
const inlineMap = resolveInlineSourceMap(code, jsUrl);
|
|
3453
|
+
if (inlineMap) return inlineMap;
|
|
3454
|
+
const mapUrl = new URL(jsUrl);
|
|
3455
|
+
mapUrl.pathname += '.map';
|
|
3456
|
+
const mapResponse = await fetcher(mapUrl.href);
|
|
3457
|
+
if (!mapResponse.ok) return null;
|
|
3458
|
+
return flattenSourceMap(await mapResponse.json(), mapUrl.href);
|
|
3459
|
+
};
|
|
3460
|
+
const loadSourceMapWithCache = async ({ jsUrl, cache, force = false, origin, fetcher = fetch })=>{
|
|
3461
|
+
const normalizedUrl = normalizeJavaScriptUrl(jsUrl, {
|
|
3462
|
+
origin
|
|
3463
|
+
});
|
|
3464
|
+
if (!normalizedUrl) return null;
|
|
3465
|
+
if (!force && cache.has(normalizedUrl)) return cache.get(normalizedUrl) ?? null;
|
|
3466
|
+
try {
|
|
3467
|
+
const sourceMap = await fetchSourceMap(normalizedUrl, fetcher);
|
|
3468
|
+
cache.set(normalizedUrl, sourceMap);
|
|
3469
|
+
return sourceMap;
|
|
3470
|
+
} catch {
|
|
3471
|
+
cache.set(normalizedUrl, null);
|
|
3472
|
+
return null;
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
const resolveCoverageResourceUrl = (value)=>{
|
|
3476
|
+
try {
|
|
3477
|
+
const url = new URL(value);
|
|
3478
|
+
return 'http:' === url.protocol || 'https:' === url.protocol ? url.href : null;
|
|
3479
|
+
} catch {
|
|
3480
|
+
return null;
|
|
3481
|
+
}
|
|
3482
|
+
};
|
|
3483
|
+
const resolveProjectHttpSource = (source, rootPath, projectOrigin)=>{
|
|
3484
|
+
let sourceUrl;
|
|
3485
|
+
try {
|
|
3486
|
+
sourceUrl = new URL(source);
|
|
3487
|
+
} catch {
|
|
3488
|
+
return source;
|
|
3489
|
+
}
|
|
3490
|
+
if ('http:' !== sourceUrl.protocol && 'https:' !== sourceUrl.protocol || sourceUrl.origin !== projectOrigin) return source;
|
|
3491
|
+
let sourcePath = sourceUrl.pathname;
|
|
3492
|
+
try {
|
|
3493
|
+
sourcePath = decodeURIComponent(sourcePath);
|
|
3494
|
+
} catch {}
|
|
3495
|
+
return external_pathe_resolve(rootPath, sourcePath.replace(/^\/+/, ''));
|
|
3496
|
+
};
|
|
3497
|
+
const normalizeSourceMap = (sourceMap, rootPath, sourceMapUrl, scriptOrigin, projectOrigin)=>{
|
|
3498
|
+
const webpackSourceRoot = 'string' == typeof sourceMap.sourceRoot && sourceMap.sourceRoot.startsWith('webpack:') ? sourceMap.sourceRoot : void 0;
|
|
3499
|
+
const resolvedSources = new TraceMap(sourceMap, sourceMapUrl).resolvedSources;
|
|
3500
|
+
const { sourceRoot: _, ...sourceMapWithoutRoot } = sourceMap;
|
|
3501
|
+
return {
|
|
3502
|
+
...sourceMapWithoutRoot,
|
|
3503
|
+
sources: sourceMap.sources.map((source, index)=>{
|
|
3504
|
+
if ('string' != typeof source) return source;
|
|
3505
|
+
if ('rstest runtime' === source || source.startsWith('webpack/runtime/') || source.startsWith('data:') || source.startsWith('blob:')) return source;
|
|
3506
|
+
if (external_pathe_isAbsolute(source)) return source;
|
|
3507
|
+
let sourceUrl;
|
|
3508
|
+
try {
|
|
3509
|
+
sourceUrl = webpackSourceRoot ? new URL(source, webpackSourceRoot) : new URL(source);
|
|
3510
|
+
} catch {
|
|
3511
|
+
const resolvedSource = resolvedSources[index] ?? source;
|
|
3512
|
+
return resolveProjectHttpSource(resolvedSource, rootPath, projectOrigin);
|
|
3513
|
+
}
|
|
3514
|
+
if ('webpack:' !== sourceUrl.protocol) {
|
|
3515
|
+
const resolvedSource = resolvedSources[index] ?? source;
|
|
3516
|
+
return resolveProjectHttpSource(resolvedSource, rootPath, projectOrigin);
|
|
3517
|
+
}
|
|
3518
|
+
let sourcePath = sourceUrl.pathname;
|
|
3519
|
+
try {
|
|
3520
|
+
sourcePath = decodeURIComponent(sourcePath);
|
|
3521
|
+
} catch {}
|
|
3522
|
+
sourcePath = sourcePath.replace(/^\/+/, '');
|
|
3523
|
+
if ('rstest runtime' === sourcePath || sourcePath.startsWith('data:') || sourcePath.startsWith('blob:')) return sourcePath;
|
|
3524
|
+
if (sourcePath.startsWith('webpack/runtime/')) return source;
|
|
3525
|
+
return scriptOrigin === projectOrigin ? external_pathe_resolve(rootPath, sourcePath) : new URL(sourcePath, `${scriptOrigin}/`).href;
|
|
3526
|
+
})
|
|
3527
|
+
};
|
|
3528
|
+
};
|
|
3529
|
+
const takeBrowserV8Coverage = async ({ collector, page, projectUrl, rootPath, fetchTimeout, sourceMapCache, resourceStore })=>{
|
|
3530
|
+
const rawEntries = await collector.take(page);
|
|
3531
|
+
const projectOrigin = new URL(projectUrl).origin;
|
|
3532
|
+
const entries = [];
|
|
3533
|
+
const assetFiles = {};
|
|
3534
|
+
const sourceMaps = {};
|
|
3535
|
+
await Promise.all(rawEntries.map(async (entry)=>{
|
|
3536
|
+
const url = resolveCoverageResourceUrl(entry.url);
|
|
3537
|
+
const cacheUrl = normalizeJavaScriptUrl(entry.url);
|
|
3538
|
+
if (!url || !cacheUrl || !entry.source) return;
|
|
3539
|
+
const inlineSourceMap = resolveInlineSourceMap(entry.source, url);
|
|
3540
|
+
const loadedSourceMap = inlineSourceMap ? null : await loadSourceMapForSource({
|
|
3541
|
+
jsUrl: url,
|
|
3542
|
+
signal: fetchTimeout > 0 ? AbortSignal.timeout(fetchTimeout) : void 0,
|
|
3543
|
+
source: entry.source
|
|
3544
|
+
});
|
|
3545
|
+
const sourceMap = inlineSourceMap ?? loadedSourceMap?.sourceMap;
|
|
3546
|
+
const normalizedSourceMap = sourceMap ? normalizeSourceMap(sourceMap, rootPath, loadedSourceMap?.sourceMapUrl ?? url, new URL(url).origin, projectOrigin) : void 0;
|
|
3547
|
+
const filePath = url;
|
|
3548
|
+
entries.push({
|
|
3549
|
+
url,
|
|
3550
|
+
scriptId: entry.scriptId,
|
|
3551
|
+
filePath,
|
|
3552
|
+
functions: entry.functions
|
|
3553
|
+
});
|
|
3554
|
+
if (resourceStore?.assetFiles.get(filePath) !== entry.source) {
|
|
3555
|
+
assetFiles[filePath] = entry.source;
|
|
3556
|
+
resourceStore?.assetFiles.set(filePath, entry.source);
|
|
3557
|
+
}
|
|
3558
|
+
if (normalizedSourceMap) {
|
|
3559
|
+
sourceMapCache.set(cacheUrl, normalizedSourceMap);
|
|
3560
|
+
const serializedSourceMap = JSON.stringify(normalizedSourceMap);
|
|
3561
|
+
if (resourceStore?.sourceMaps.get(filePath) !== serializedSourceMap) {
|
|
3562
|
+
sourceMaps[filePath] = serializedSourceMap;
|
|
3563
|
+
resourceStore?.sourceMaps.set(filePath, serializedSourceMap);
|
|
3564
|
+
}
|
|
3565
|
+
} else {
|
|
3566
|
+
sourceMapCache.delete(cacheUrl);
|
|
3567
|
+
resourceStore?.sourceMaps.delete(filePath);
|
|
3568
|
+
}
|
|
3569
|
+
}));
|
|
3570
|
+
if (!entries.length) return null;
|
|
3571
|
+
return {
|
|
3572
|
+
entries,
|
|
3573
|
+
...Object.keys(assetFiles).length || Object.keys(sourceMaps).length ? {
|
|
3574
|
+
options: {
|
|
3575
|
+
assetFiles,
|
|
3576
|
+
sourceMaps
|
|
3577
|
+
}
|
|
3578
|
+
} : {},
|
|
3579
|
+
root: rootPath
|
|
3580
|
+
};
|
|
3581
|
+
};
|
|
3311
3582
|
function createPromiseWithResolvers() {
|
|
3312
3583
|
let resolve;
|
|
3313
3584
|
let reject;
|
|
@@ -3343,7 +3614,7 @@ function createBirpc($functions, options) {
|
|
|
3343
3614
|
const req = {
|
|
3344
3615
|
m: method,
|
|
3345
3616
|
a: args,
|
|
3346
|
-
t:
|
|
3617
|
+
t: "q"
|
|
3347
3618
|
};
|
|
3348
3619
|
if (optional) req.o = true;
|
|
3349
3620
|
const send = async (_req)=>post(serialize(_req));
|
|
@@ -3394,7 +3665,7 @@ function createBirpc($functions, options) {
|
|
|
3394
3665
|
$call: (method, ...args)=>_call(method, args, false),
|
|
3395
3666
|
$callOptional: (method, ...args)=>_call(method, args, false, true),
|
|
3396
3667
|
$callEvent: (method, ...args)=>_call(method, args, true),
|
|
3397
|
-
$callRaw: (options
|
|
3668
|
+
$callRaw: (options)=>_call(options.method, options.args, options.event, options.optional),
|
|
3398
3669
|
$rejectPendingCalls,
|
|
3399
3670
|
get $closed () {
|
|
3400
3671
|
return $closed;
|
|
@@ -3407,7 +3678,7 @@ function createBirpc($functions, options) {
|
|
|
3407
3678
|
};
|
|
3408
3679
|
rpc = proxify ? new Proxy({}, {
|
|
3409
3680
|
get (_, method) {
|
|
3410
|
-
if (Object.
|
|
3681
|
+
if (Object.hasOwn(builtinMethods, method)) return builtinMethods[method];
|
|
3411
3682
|
if ("then" === method && !eventNames.includes("then") && !("then" in $functions)) return;
|
|
3412
3683
|
const sendEvent = (...args)=>_call(method, args, true);
|
|
3413
3684
|
if (eventNames.includes(method)) {
|
|
@@ -3451,7 +3722,7 @@ function createBirpc($functions, options) {
|
|
|
3451
3722
|
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
|
|
3452
3723
|
return;
|
|
3453
3724
|
}
|
|
3454
|
-
if (msg.t
|
|
3725
|
+
if ("q" === msg.t) {
|
|
3455
3726
|
const { m: method, a: args, o: optional } = msg;
|
|
3456
3727
|
let result, error;
|
|
3457
3728
|
let fn = await (resolver ? resolver.call(rpc, method, $functions[method]) : $functions[method]);
|
|
@@ -3468,7 +3739,7 @@ function createBirpc($functions, options) {
|
|
|
3468
3739
|
}
|
|
3469
3740
|
if (!error) try {
|
|
3470
3741
|
await post(serialize({
|
|
3471
|
-
t:
|
|
3742
|
+
t: "s",
|
|
3472
3743
|
i: msg.i,
|
|
3473
3744
|
r: result
|
|
3474
3745
|
}), ...extra);
|
|
@@ -3479,7 +3750,7 @@ function createBirpc($functions, options) {
|
|
|
3479
3750
|
}
|
|
3480
3751
|
try {
|
|
3481
3752
|
await post(serialize({
|
|
3482
|
-
t:
|
|
3753
|
+
t: "s",
|
|
3483
3754
|
i: msg.i,
|
|
3484
3755
|
e: error
|
|
3485
3756
|
}), ...extra);
|
|
@@ -3507,16 +3778,20 @@ class ContainerRpcManager {
|
|
|
3507
3778
|
rpc = null;
|
|
3508
3779
|
methods;
|
|
3509
3780
|
onDisconnect;
|
|
3781
|
+
onAttach;
|
|
3510
3782
|
detachActiveSocketListeners = null;
|
|
3511
|
-
|
|
3783
|
+
transportEpoch = 0;
|
|
3784
|
+
constructor(wss, methods, onDisconnect, onAttach){
|
|
3512
3785
|
this.wss = wss;
|
|
3513
3786
|
this.methods = methods;
|
|
3514
3787
|
this.onDisconnect = onDisconnect;
|
|
3788
|
+
this.onAttach = onAttach;
|
|
3515
3789
|
this.setupConnectionHandler();
|
|
3516
3790
|
}
|
|
3517
|
-
updateMethods(methods, onDisconnect) {
|
|
3791
|
+
updateMethods(methods, onDisconnect, onAttach) {
|
|
3518
3792
|
this.methods = methods;
|
|
3519
3793
|
this.onDisconnect = onDisconnect;
|
|
3794
|
+
this.onAttach = onAttach;
|
|
3520
3795
|
if (this.ws && this.ws.readyState === this.ws.OPEN) this.attachWebSocket(this.ws);
|
|
3521
3796
|
}
|
|
3522
3797
|
setupConnectionHandler() {
|
|
@@ -3529,6 +3804,8 @@ class ContainerRpcManager {
|
|
|
3529
3804
|
attachWebSocket(ws) {
|
|
3530
3805
|
this.detachActiveSocketListeners?.();
|
|
3531
3806
|
if (this.rpc && !this.rpc.$closed) this.rpc.$close(new Error('Container RPC transport reattached'));
|
|
3807
|
+
this.transportEpoch += 1;
|
|
3808
|
+
this.onAttach?.(this.transportEpoch);
|
|
3532
3809
|
this.ws = ws;
|
|
3533
3810
|
const messageHandlers = new WeakMap();
|
|
3534
3811
|
this.rpc = createBirpc(this.methods, {
|
|
@@ -3578,30 +3855,19 @@ class ContainerRpcManager {
|
|
|
3578
3855
|
reattach(ws) {
|
|
3579
3856
|
this.attachWebSocket(ws);
|
|
3580
3857
|
}
|
|
3581
|
-
async notifyTestFileUpdate(files) {
|
|
3582
|
-
await this.rpc?.onTestFileUpdate(files);
|
|
3858
|
+
async notifyTestFileUpdate(files, version) {
|
|
3859
|
+
await this.rpc?.onTestFileUpdate(files, version);
|
|
3583
3860
|
}
|
|
3584
3861
|
async updateHostConfig(config) {
|
|
3585
3862
|
await this.rpc?.onHostConfigUpdate(config);
|
|
3586
3863
|
}
|
|
3587
|
-
async reloadTestFile(testFile, testNamePattern) {
|
|
3864
|
+
async reloadTestFile(testFile, runId, testNamePattern) {
|
|
3588
3865
|
logger.debug(`[Browser UI] reloadTestFile called, rpc: ${this.rpc ? 'exists' : 'null'}, ws: ${this.ws ? 'exists' : 'null'}`);
|
|
3589
3866
|
if (!this.rpc) throw new Error('Browser UI RPC not available for reloadTestFile');
|
|
3590
|
-
logger.debug(`[Browser UI] Calling reloadTestFile: ${testFile}`);
|
|
3591
|
-
|
|
3867
|
+
logger.debug(`[Browser UI] Calling reloadTestFile: ${testFile} (run ${runId})`);
|
|
3868
|
+
await this.rpc.reloadTestFile(testFile, runId, testNamePattern);
|
|
3592
3869
|
}
|
|
3593
3870
|
}
|
|
3594
|
-
const createHeadedSerialTaskQueue = ()=>{
|
|
3595
|
-
let queue = Promise.resolve();
|
|
3596
|
-
const enqueue = (task)=>{
|
|
3597
|
-
const next = queue.then(task);
|
|
3598
|
-
queue = next.catch(()=>{});
|
|
3599
|
-
return next;
|
|
3600
|
-
};
|
|
3601
|
-
return {
|
|
3602
|
-
enqueue
|
|
3603
|
-
};
|
|
3604
|
-
};
|
|
3605
3871
|
const getFileTaskId = (testPath)=>`file:${testPath}`;
|
|
3606
3872
|
const toError = (error)=>error instanceof Error ? error : new Error(String(error));
|
|
3607
3873
|
const createDeferredPromise = ()=>{
|
|
@@ -3617,6 +3883,105 @@ const createDeferredPromise = ()=>{
|
|
|
3617
3883
|
reject
|
|
3618
3884
|
};
|
|
3619
3885
|
};
|
|
3886
|
+
const RUN_BOOT_DEADLINE_MS = 45000;
|
|
3887
|
+
const createHeadedRunRegistry = ()=>{
|
|
3888
|
+
const runsById = new Map();
|
|
3889
|
+
let transportEpoch = 0;
|
|
3890
|
+
const settle = (record, error)=>{
|
|
3891
|
+
if (!runsById.delete(record.runId)) return;
|
|
3892
|
+
clearTimeout(record.bootTimer);
|
|
3893
|
+
clearTimeout(record.cleanupTimer);
|
|
3894
|
+
if (error) record.deferred.reject(error);
|
|
3895
|
+
else record.deferred.resolve();
|
|
3896
|
+
};
|
|
3897
|
+
const sweepOpen = (match, error)=>{
|
|
3898
|
+
for (const record of runsById.values())if ('open' === record.phase && match(record)) settle(record, error);
|
|
3899
|
+
};
|
|
3900
|
+
return {
|
|
3901
|
+
mint (testPath) {
|
|
3902
|
+
sweepOpen((record)=>record.testPath === testPath);
|
|
3903
|
+
const runId = randomUUID();
|
|
3904
|
+
const bootTimer = setTimeout(()=>{
|
|
3905
|
+
const record = runsById.get(runId);
|
|
3906
|
+
if (record) settle(record, new Error(`Headed run for ${testPath} produced no message within ${RUN_BOOT_DEADLINE_MS / 1000}s; its document never booted`));
|
|
3907
|
+
}, RUN_BOOT_DEADLINE_MS);
|
|
3908
|
+
bootTimer.unref?.();
|
|
3909
|
+
const record = {
|
|
3910
|
+
runId,
|
|
3911
|
+
testPath,
|
|
3912
|
+
epoch: transportEpoch,
|
|
3913
|
+
phase: 'open',
|
|
3914
|
+
deferred: createDeferredPromise(),
|
|
3915
|
+
bootTimer
|
|
3916
|
+
};
|
|
3917
|
+
runsById.set(runId, record);
|
|
3918
|
+
return {
|
|
3919
|
+
runId,
|
|
3920
|
+
settled: record.deferred.promise
|
|
3921
|
+
};
|
|
3922
|
+
},
|
|
3923
|
+
admit (runId, terminal) {
|
|
3924
|
+
const record = runsById.get(runId);
|
|
3925
|
+
if (!record) return false;
|
|
3926
|
+
clearTimeout(record.bootTimer);
|
|
3927
|
+
if (!terminal) return true;
|
|
3928
|
+
if ('open' !== record.phase) return false;
|
|
3929
|
+
record.phase = 'claimed';
|
|
3930
|
+
return true;
|
|
3931
|
+
},
|
|
3932
|
+
armCleanupDeadline (runId, ms, onExpire) {
|
|
3933
|
+
const record = runsById.get(runId);
|
|
3934
|
+
if (!record || 'open' !== record.phase) return;
|
|
3935
|
+
clearTimeout(record.cleanupTimer);
|
|
3936
|
+
const cleanupTimer = setTimeout(()=>{
|
|
3937
|
+
const current = runsById.get(runId);
|
|
3938
|
+
if (!current || 'open' !== current.phase) return;
|
|
3939
|
+
current.cleanupTimer = void 0;
|
|
3940
|
+
current.phase = 'claimed';
|
|
3941
|
+
onExpire();
|
|
3942
|
+
}, ms);
|
|
3943
|
+
cleanupTimer.unref?.();
|
|
3944
|
+
record.cleanupTimer = cleanupTimer;
|
|
3945
|
+
},
|
|
3946
|
+
disarmCleanupDeadline (runId) {
|
|
3947
|
+
const record = runsById.get(runId);
|
|
3948
|
+
if (record) {
|
|
3949
|
+
clearTimeout(record.cleanupTimer);
|
|
3950
|
+
record.cleanupTimer = void 0;
|
|
3951
|
+
}
|
|
3952
|
+
},
|
|
3953
|
+
resolve (runId) {
|
|
3954
|
+
const record = runsById.get(runId);
|
|
3955
|
+
if (record) settle(record);
|
|
3956
|
+
},
|
|
3957
|
+
reject (runId, error) {
|
|
3958
|
+
const record = runsById.get(runId);
|
|
3959
|
+
if (record) settle(record, toError(error));
|
|
3960
|
+
},
|
|
3961
|
+
retainPaths (paths) {
|
|
3962
|
+
const retained = new Set(paths);
|
|
3963
|
+
sweepOpen((record)=>!retained.has(record.testPath));
|
|
3964
|
+
},
|
|
3965
|
+
rejectAll (error) {
|
|
3966
|
+
sweepOpen(()=>true, error);
|
|
3967
|
+
},
|
|
3968
|
+
setTransportEpoch (epoch) {
|
|
3969
|
+
transportEpoch = epoch;
|
|
3970
|
+
sweepOpen((record)=>record.epoch < epoch, new Error('Container transport was replaced before the run completed'));
|
|
3971
|
+
}
|
|
3972
|
+
};
|
|
3973
|
+
};
|
|
3974
|
+
const createHeadedSerialTaskQueue = ()=>{
|
|
3975
|
+
let queue = Promise.resolve();
|
|
3976
|
+
const enqueue = (task)=>{
|
|
3977
|
+
const next = queue.then(task);
|
|
3978
|
+
queue = next.catch(()=>{});
|
|
3979
|
+
return next;
|
|
3980
|
+
};
|
|
3981
|
+
return {
|
|
3982
|
+
enqueue
|
|
3983
|
+
};
|
|
3984
|
+
};
|
|
3620
3985
|
const claimHeadedCycleScope = (testPaths, currentTestFiles, pendingTestNamePatterns)=>{
|
|
3621
3986
|
const scope = [];
|
|
3622
3987
|
const filesByPath = new Map(currentTestFiles.map((file)=>[
|
|
@@ -3636,131 +4001,161 @@ const claimHeadedCycleScope = (testPaths, currentTestFiles, pendingTestNamePatte
|
|
|
3636
4001
|
}
|
|
3637
4002
|
return scope;
|
|
3638
4003
|
};
|
|
3639
|
-
const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptions, isWatchMode, createDispatchRouter, handlers: {
|
|
4004
|
+
const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptions, v8Coverage, projectRoots, isWatchMode, createDispatchRouter, handlers: { handleTestFileComplete }, fatalErrorRef, watchSignals, setDispatchPageResolver, createWatchSession, collectProjectEntries, logWatchReady, destroyRuntime })=>{
|
|
3640
4005
|
const { browser, browserLaunchOptions, watchState, wss } = runtime;
|
|
4006
|
+
const containerUrl = `http://localhost:${runtime.containerServer.port}/`;
|
|
3641
4007
|
let currentTestFiles = allTestFiles;
|
|
3642
4008
|
const RUNNER_FRAMES_READY_TIMEOUT_MS = 30000;
|
|
3643
|
-
let
|
|
3644
|
-
const
|
|
3645
|
-
const
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
4009
|
+
let ackedFrameSetVersion = 0;
|
|
4010
|
+
const frameSetWaiters = new Set();
|
|
4011
|
+
const markFrameSetReady = (version)=>{
|
|
4012
|
+
if (version > watchState.headedFileSetVersion) return;
|
|
4013
|
+
if (version > ackedFrameSetVersion) ackedFrameSetVersion = version;
|
|
4014
|
+
for (const waiter of frameSetWaiters)if (waiter.version <= ackedFrameSetVersion) {
|
|
4015
|
+
clearTimeout(waiter.timeoutId);
|
|
4016
|
+
frameSetWaiters.delete(waiter);
|
|
4017
|
+
waiter.resolve();
|
|
4018
|
+
}
|
|
3653
4019
|
};
|
|
3654
|
-
const
|
|
3655
|
-
|
|
3656
|
-
if (currentRunnerFramesSignature === signature) return;
|
|
4020
|
+
const waitForFrameSet = async (version)=>{
|
|
4021
|
+
if (ackedFrameSetVersion >= version) return;
|
|
3657
4022
|
await new Promise((resolve, reject)=>{
|
|
3658
|
-
const
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
const onReady = ()=>{
|
|
3666
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
3667
|
-
cleanup();
|
|
3668
|
-
resolve();
|
|
4023
|
+
const waiter = {
|
|
4024
|
+
version,
|
|
4025
|
+
resolve,
|
|
4026
|
+
timeoutId: setTimeout(()=>{
|
|
4027
|
+
frameSetWaiters.delete(waiter);
|
|
4028
|
+
reject(new Error(`Timed out waiting for headed runner frames to be ready (file-set version ${version}).`));
|
|
4029
|
+
}, RUNNER_FRAMES_READY_TIMEOUT_MS)
|
|
3669
4030
|
};
|
|
3670
|
-
|
|
3671
|
-
cleanup();
|
|
3672
|
-
reject(new Error(`Timed out waiting for headed runner frames to be ready for ${testFiles.length} file(s).`));
|
|
3673
|
-
}, RUNNER_FRAMES_READY_TIMEOUT_MS);
|
|
3674
|
-
waiters.add(onReady);
|
|
3675
|
-
runnerFramesWaiters.set(signature, waiters);
|
|
3676
|
-
if (currentRunnerFramesSignature === signature) onReady();
|
|
4031
|
+
frameSetWaiters.add(waiter);
|
|
3677
4032
|
});
|
|
3678
4033
|
};
|
|
3679
|
-
const
|
|
4034
|
+
const findTestFileInfo = (testFile)=>{
|
|
3680
4035
|
const normalizedTestFile = normalize(testFile);
|
|
3681
|
-
|
|
4036
|
+
return currentTestFiles.find((file)=>file.testPath === normalizedTestFile);
|
|
4037
|
+
};
|
|
4038
|
+
const getTestFileInfo = (testFile)=>{
|
|
4039
|
+
const fileInfo = findTestFileInfo(testFile);
|
|
3682
4040
|
if (!fileInfo) throw new Error(`Unknown browser test file: ${JSON.stringify(testFile)}`);
|
|
3683
4041
|
return fileInfo;
|
|
3684
4042
|
};
|
|
3685
4043
|
let containerContext;
|
|
3686
4044
|
let containerPage;
|
|
3687
4045
|
let isNewPage = false;
|
|
4046
|
+
const attachContainerPageHandlers = (activeContext, activePage)=>{
|
|
4047
|
+
activePage.on('popup', async (popup)=>{
|
|
4048
|
+
await popup.close().catch(()=>{});
|
|
4049
|
+
});
|
|
4050
|
+
activeContext.on('page', async (page)=>{
|
|
4051
|
+
if (page !== activePage) await page.close().catch(()=>{});
|
|
4052
|
+
});
|
|
4053
|
+
activePage.on('console', (msg)=>{
|
|
4054
|
+
const text = msg.text();
|
|
4055
|
+
if (text.startsWith('[Container]') || text.startsWith('[Runner]')) return void logger.log(color.gray(`[Browser Console] ${text}`));
|
|
4056
|
+
logger.debug(`[Browser Console] ${text}`);
|
|
4057
|
+
});
|
|
4058
|
+
};
|
|
4059
|
+
const createContainerPage = async ()=>{
|
|
4060
|
+
const nextContext = await browser.newContext({
|
|
4061
|
+
providerOptions: browserLaunchOptions.providerOptions,
|
|
4062
|
+
viewport: null
|
|
4063
|
+
});
|
|
4064
|
+
const nextPage = await nextContext.newPage();
|
|
4065
|
+
attachContainerPageHandlers(nextContext, nextPage);
|
|
4066
|
+
return {
|
|
4067
|
+
context: nextContext,
|
|
4068
|
+
page: nextPage
|
|
4069
|
+
};
|
|
4070
|
+
};
|
|
3688
4071
|
if (isWatchMode && runtime.containerPage && runtime.containerContext) {
|
|
3689
4072
|
containerContext = runtime.containerContext;
|
|
3690
4073
|
containerPage = runtime.containerPage;
|
|
3691
4074
|
logger.log(color.gray('\n[Watch] Reusing existing container page\n'));
|
|
3692
4075
|
} else {
|
|
3693
4076
|
isNewPage = true;
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
});
|
|
3698
|
-
containerPage = await containerContext.newPage();
|
|
3699
|
-
containerPage.on('popup', async (popup)=>{
|
|
3700
|
-
await popup.close().catch(()=>{});
|
|
3701
|
-
});
|
|
3702
|
-
containerContext.on('page', async (page)=>{
|
|
3703
|
-
if (page !== containerPage) await page.close().catch(()=>{});
|
|
3704
|
-
});
|
|
4077
|
+
const created = await createContainerPage();
|
|
4078
|
+
containerContext = created.context;
|
|
4079
|
+
containerPage = created.page;
|
|
3705
4080
|
if (isWatchMode) {
|
|
3706
4081
|
runtime.containerPage = containerPage;
|
|
3707
4082
|
runtime.containerContext = containerContext;
|
|
3708
4083
|
}
|
|
3709
|
-
containerPage.on('console', (msg)=>{
|
|
3710
|
-
const text = msg.text();
|
|
3711
|
-
if (text.startsWith('[Container]') || text.startsWith('[Runner]')) logger.log(color.gray(`[Browser Console] ${text}`));
|
|
3712
|
-
});
|
|
3713
4084
|
}
|
|
3714
4085
|
setDispatchPageResolver(()=>({
|
|
3715
4086
|
containerPage
|
|
3716
4087
|
}));
|
|
3717
4088
|
const dispatchRouter = createDispatchRouter();
|
|
3718
4089
|
const headedReloadQueue = createHeadedSerialTaskQueue();
|
|
3719
|
-
|
|
3720
|
-
let
|
|
3721
|
-
|
|
4090
|
+
let rawCoverage = [];
|
|
4091
|
+
let coverageProject;
|
|
4092
|
+
let coverageStarted = false;
|
|
4093
|
+
const startCoverage = async (projectName)=>{
|
|
4094
|
+
if (!v8Coverage) return;
|
|
4095
|
+
await v8Coverage.start(containerPage);
|
|
4096
|
+
coverageStarted = true;
|
|
4097
|
+
coverageProject = projectName;
|
|
3722
4098
|
};
|
|
3723
|
-
const
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
pending.deferred.reject(error);
|
|
4099
|
+
const collectCoverage = async ()=>{
|
|
4100
|
+
if (!coverageStarted || !v8Coverage) return;
|
|
4101
|
+
coverageStarted = false;
|
|
4102
|
+
const coverage = await v8Coverage.take(containerPage, projectRoots.get(coverageProject ?? '') ?? context.rootPath, coverageProject);
|
|
4103
|
+
if (coverage) rawCoverage.push(coverage);
|
|
3729
4104
|
};
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
pendingHeadedReloads.delete(testPath);
|
|
3733
|
-
pending.deferred.reject(error);
|
|
3734
|
-
}
|
|
4105
|
+
let enqueueHeadedReload = async (_file, _testNamePattern)=>{
|
|
4106
|
+
throw new Error('Headed reload queue is not initialized');
|
|
3735
4107
|
};
|
|
3736
|
-
const
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
4108
|
+
const isCurrentTestFile = (testPath)=>void 0 !== findTestFileInfo(testPath);
|
|
4109
|
+
const runs = watchState.headedRuns ??= createHeadedRunRegistry();
|
|
4110
|
+
const recoverHeadedContainer = async ()=>{
|
|
4111
|
+
ackedFrameSetVersion = 0;
|
|
4112
|
+
await containerContext.close().catch(()=>{});
|
|
4113
|
+
const created = await createContainerPage();
|
|
4114
|
+
containerContext = created.context;
|
|
4115
|
+
containerPage = created.page;
|
|
4116
|
+
if (isWatchMode) {
|
|
4117
|
+
runtime.containerPage = containerPage;
|
|
4118
|
+
runtime.containerContext = containerContext;
|
|
3741
4119
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
runId,
|
|
3745
|
-
deferred
|
|
4120
|
+
await containerPage.goto(containerUrl, {
|
|
4121
|
+
waitUntil: 'load'
|
|
3746
4122
|
});
|
|
3747
|
-
|
|
3748
|
-
};
|
|
3749
|
-
const resolvePendingHeadedReload = (testPath, runId)=>{
|
|
3750
|
-
const pending = pendingHeadedReloads.get(testPath);
|
|
3751
|
-
if (!pending) return;
|
|
3752
|
-
if (runId && pending.runId !== runId) return void logger.debug(`[Browser UI] Ignoring stale file-complete for ${testPath}. current=${pending.runId}, incoming=${runId}`);
|
|
3753
|
-
pendingHeadedReloads.delete(testPath);
|
|
3754
|
-
pending.deferred.resolve();
|
|
4123
|
+
await waitForFrameSet(watchState.headedFileSetVersion);
|
|
3755
4124
|
};
|
|
4125
|
+
dispatchRouter.register(DISPATCH_NAMESPACE_FILE_CLEANUP, async (request)=>{
|
|
4126
|
+
const { runId } = request;
|
|
4127
|
+
if (!runId) return;
|
|
4128
|
+
if ('start' === request.method) {
|
|
4129
|
+
const payload = request.args;
|
|
4130
|
+
runs.armCleanupDeadline(runId, FIXTURE_CLEANUP_TIMEOUT_MS, ()=>{
|
|
4131
|
+
(async ()=>{
|
|
4132
|
+
try {
|
|
4133
|
+
await collectCoverage();
|
|
4134
|
+
await handleTestFileComplete(createFileCleanupTimeoutResult({
|
|
4135
|
+
message: `File fixture cleanup did not finish within ${FIXTURE_CLEANUP_TIMEOUT_MS}ms`,
|
|
4136
|
+
projectName: payload.projectName,
|
|
4137
|
+
result: payload.result,
|
|
4138
|
+
testPath: payload.testPath
|
|
4139
|
+
}));
|
|
4140
|
+
await recoverHeadedContainer();
|
|
4141
|
+
runs.resolve(runId);
|
|
4142
|
+
} catch (error) {
|
|
4143
|
+
runs.reject(runId, toError(error));
|
|
4144
|
+
}
|
|
4145
|
+
})();
|
|
4146
|
+
});
|
|
4147
|
+
} else if ('end' === request.method) runs.disarmCleanupDeadline(runId);
|
|
4148
|
+
});
|
|
3756
4149
|
const reloadTestFileAndWait = async (file, testNamePattern)=>{
|
|
3757
|
-
|
|
4150
|
+
await startCoverage(file.projectName);
|
|
4151
|
+
const { runId, settled } = runs.mint(file.testPath);
|
|
4152
|
+
rpcManager.reloadTestFile(file.testPath, runId, testNamePattern).catch((error)=>runs.reject(runId, error));
|
|
3758
4153
|
try {
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
4154
|
+
await settled;
|
|
4155
|
+
} finally{
|
|
4156
|
+
await collectCoverage().catch((error)=>{
|
|
4157
|
+
logger.debug(`[Headed] Failed to flush coverage: ${error}`);
|
|
4158
|
+
});
|
|
3764
4159
|
}
|
|
3765
4160
|
};
|
|
3766
4161
|
let runUiRequestedRerun = async (file, testNamePattern)=>{
|
|
@@ -3775,66 +4170,73 @@ const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptio
|
|
|
3775
4170
|
await runUiRequestedRerun(getTestFileInfo(testFile), testNamePattern);
|
|
3776
4171
|
},
|
|
3777
4172
|
async getTestFiles () {
|
|
3778
|
-
return
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
},
|
|
3783
|
-
async onTestFileStart (payload) {
|
|
3784
|
-
await handleTestFileStart(payload);
|
|
4173
|
+
return {
|
|
4174
|
+
files: currentTestFiles,
|
|
4175
|
+
version: watchState.headedFileSetVersion
|
|
4176
|
+
};
|
|
3785
4177
|
},
|
|
3786
|
-
async
|
|
3787
|
-
|
|
4178
|
+
async onFrameSetReady (version) {
|
|
4179
|
+
markFrameSetReady(version);
|
|
3788
4180
|
},
|
|
3789
|
-
async
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
4181
|
+
async dispatch (request) {
|
|
4182
|
+
const { method, runId } = request;
|
|
4183
|
+
const isTerminal = "runner" === request.namespace && ('file-complete' === method || 'fatal' === method);
|
|
4184
|
+
if (!runId || !runs.admit(runId, isTerminal)) {
|
|
4185
|
+
logger.debug(`[Headed] Dropped stale "${method}" from run ${runId ?? '(none)'}`);
|
|
4186
|
+
return {
|
|
4187
|
+
requestId: request.requestId,
|
|
4188
|
+
stale: true
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
let coverageError;
|
|
4192
|
+
if (isTerminal) try {
|
|
4193
|
+
await collectCoverage();
|
|
3793
4194
|
} catch (error) {
|
|
3794
|
-
|
|
3795
|
-
throw error;
|
|
4195
|
+
coverageError = toError(error);
|
|
3796
4196
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
4197
|
+
const response = await dispatchRouter.dispatch(request);
|
|
4198
|
+
if (!isTerminal) return response;
|
|
4199
|
+
if ('file-complete' === method) {
|
|
4200
|
+
const failure = coverageError ?? (response.error ? new Error(response.error) : void 0);
|
|
4201
|
+
if (failure) runs.reject(runId, failure);
|
|
4202
|
+
else runs.resolve(runId);
|
|
4203
|
+
} else {
|
|
4204
|
+
if (coverageError) logger.error(`[Headed] Failed to take coverage during fatal handling: ${coverageError.message}`);
|
|
4205
|
+
const payload = request.args;
|
|
4206
|
+
const error = new Error(payload?.message ?? 'Fatal browser error');
|
|
4207
|
+
if (payload?.stack) error.stack = payload.stack;
|
|
4208
|
+
runs.reject(runId, error);
|
|
4209
|
+
}
|
|
4210
|
+
return response;
|
|
3809
4211
|
}
|
|
3810
4212
|
});
|
|
3811
4213
|
let rpcManager;
|
|
3812
4214
|
if (isWatchMode && runtime.rpcManager) {
|
|
3813
4215
|
rpcManager = runtime.rpcManager;
|
|
3814
|
-
rpcManager.updateMethods(createRpcMethods(),
|
|
4216
|
+
rpcManager.updateMethods(createRpcMethods(), runs.rejectAll, runs.setTransportEpoch);
|
|
3815
4217
|
const existingWs = rpcManager.currentWebSocket;
|
|
3816
4218
|
if (existingWs) rpcManager.reattach(existingWs);
|
|
3817
4219
|
} else {
|
|
3818
|
-
rpcManager = new ContainerRpcManager(wss, createRpcMethods(),
|
|
4220
|
+
rpcManager = new ContainerRpcManager(wss, createRpcMethods(), runs.rejectAll, runs.setTransportEpoch);
|
|
3819
4221
|
if (isWatchMode) runtime.rpcManager = rpcManager;
|
|
3820
4222
|
}
|
|
3821
4223
|
if (isNewPage) {
|
|
3822
|
-
|
|
3823
|
-
const containerPort = runtime.containerServer.port;
|
|
3824
|
-
await containerPage.goto(`http://localhost:${containerPort}${pagePath}`, {
|
|
4224
|
+
await containerPage.goto(containerUrl, {
|
|
3825
4225
|
waitUntil: 'load'
|
|
3826
4226
|
});
|
|
3827
|
-
logger.log(color.cyan(`\nBrowser mode opened at
|
|
4227
|
+
logger.log(color.cyan(`\nBrowser mode opened at ${containerUrl}\n`));
|
|
3828
4228
|
}
|
|
3829
4229
|
enqueueHeadedReload = async (file, testNamePattern)=>headedReloadQueue.enqueue(async ()=>{
|
|
3830
4230
|
if (fatalErrorRef.current) return;
|
|
4231
|
+
if (!isCurrentTestFile(file.testPath)) return void logger.debug(`[Browser UI] Skipping reload for removed test file: ${file.testPath}`);
|
|
3831
4232
|
await reloadTestFileAndWait(file, testNamePattern);
|
|
3832
4233
|
});
|
|
3833
4234
|
let testTime = 0;
|
|
3834
4235
|
if (currentTestFiles.length > 0) {
|
|
3835
4236
|
const testStart = Date.now();
|
|
3836
4237
|
try {
|
|
3837
|
-
await
|
|
4238
|
+
if (rpcManager.isConnected) await rpcManager.notifyTestFileUpdate(currentTestFiles, watchState.headedFileSetVersion);
|
|
4239
|
+
await waitForFrameSet(watchState.headedFileSetVersion);
|
|
3838
4240
|
for (const file of currentTestFiles){
|
|
3839
4241
|
await enqueueHeadedReload(file);
|
|
3840
4242
|
if (fatalErrorRef.current) break;
|
|
@@ -3848,8 +4250,10 @@ const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptio
|
|
|
3848
4250
|
if (isWatchMode) {
|
|
3849
4251
|
const pendingTestNamePatterns = new Map();
|
|
3850
4252
|
const runScope = async (testPaths)=>{
|
|
4253
|
+
rawCoverage = [];
|
|
3851
4254
|
const cycleScope = claimHeadedCycleScope(testPaths, currentTestFiles, pendingTestNamePatterns);
|
|
3852
4255
|
for (const { file, testNamePattern } of cycleScope)await enqueueHeadedReload(file, testNamePattern);
|
|
4256
|
+
return rawCoverage;
|
|
3853
4257
|
};
|
|
3854
4258
|
const refreshHostConfig = async ()=>{
|
|
3855
4259
|
const refreshedHostOptions = {
|
|
@@ -3861,46 +4265,44 @@ const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptio
|
|
|
3861
4265
|
runtime.setContainerOptions(refreshedHostOptions);
|
|
3862
4266
|
await rpcManager.updateHostConfig(refreshedHostOptions);
|
|
3863
4267
|
};
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
if (
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
logWatchReady();
|
|
3883
|
-
return;
|
|
4268
|
+
const runDispatchRerunOnce = async ()=>{
|
|
4269
|
+
try {
|
|
4270
|
+
const [, newProjectEntries] = await Promise.all([
|
|
4271
|
+
refreshHostConfig(),
|
|
4272
|
+
collectProjectEntries()
|
|
4273
|
+
]);
|
|
4274
|
+
const rerunPlan = planWatchRerun({
|
|
4275
|
+
projectEntries: newProjectEntries,
|
|
4276
|
+
previousTestFiles: watchState.lastTestFiles,
|
|
4277
|
+
affectedTestFiles: drainPendingAffectedTestFiles(watchState)
|
|
4278
|
+
});
|
|
4279
|
+
commitWatchFileSetUpdate(rerunPlan.fileSetUpdate, watchState, (deletedTestPaths)=>context.updateReporterResultState([], [], deletedTestPaths));
|
|
4280
|
+
if (rerunPlan.fileSetUpdate) {
|
|
4281
|
+
currentTestFiles = rerunPlan.fileSetUpdate.currentTestFiles;
|
|
4282
|
+
const version = watchState.headedFileSetVersion += 1;
|
|
4283
|
+
runs.retainPaths(currentTestFiles.map((file)=>file.testPath));
|
|
4284
|
+
await rpcManager.notifyTestFileUpdate(currentTestFiles, version);
|
|
4285
|
+
if (currentTestFiles.length > 0) await waitForFrameSet(version);
|
|
3884
4286
|
}
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
4287
|
+
logger.log(color.cyan(rerunPlan.decision.message));
|
|
4288
|
+
if ('idle' === rerunPlan.decision.kind) return void logWatchReady();
|
|
4289
|
+
await watchSignals.signalInvalidation('empty' === rerunPlan.decision.kind ? [] : rerunPlan.decision.testPaths);
|
|
4290
|
+
} catch (error) {
|
|
4291
|
+
logWatchReady();
|
|
4292
|
+
throw error;
|
|
3891
4293
|
}
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
4294
|
+
};
|
|
4295
|
+
const dispatchRerunQueue = createHeadedSerialTaskQueue();
|
|
4296
|
+
watchSignals.setDispatchRerun(()=>dispatchRerunQueue.enqueue(runDispatchRerunOnce));
|
|
3895
4297
|
runUiRequestedRerun = async (file, testNamePattern)=>{
|
|
3896
4298
|
await refreshHostConfig();
|
|
3897
|
-
await watchSignals.signalInvalidation([
|
|
4299
|
+
const { cycle } = await watchSignals.signalInvalidation([
|
|
3898
4300
|
file.testPath
|
|
3899
4301
|
], ()=>{
|
|
3900
4302
|
if (void 0 === testNamePattern) pendingTestNamePatterns.delete(normalize(file.testPath));
|
|
3901
4303
|
else pendingTestNamePatterns.set(normalize(file.testPath), testNamePattern);
|
|
3902
4304
|
});
|
|
3903
|
-
await
|
|
4305
|
+
await cycle;
|
|
3904
4306
|
};
|
|
3905
4307
|
watchSession = createWatchSession(runScope);
|
|
3906
4308
|
}
|
|
@@ -3915,6 +4317,7 @@ const createHeadedScheduler = async ({ context, runtime, allTestFiles, hostOptio
|
|
|
3915
4317
|
};
|
|
3916
4318
|
return {
|
|
3917
4319
|
testTime,
|
|
4320
|
+
rawCoverage,
|
|
3918
4321
|
watchSession,
|
|
3919
4322
|
close: closeContainerRuntime
|
|
3920
4323
|
};
|
|
@@ -4053,8 +4456,12 @@ class RunnerSessionRegistry {
|
|
|
4053
4456
|
this.sessionIdByTestFile.clear();
|
|
4054
4457
|
}
|
|
4055
4458
|
}
|
|
4056
|
-
const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions, projectServers, allTestFiles, projectRuntimeConfigs, hostOptions, watchState, isWatchMode, createDispatchRouter, handlers: { handleFatal, handleTestFileComplete }, watchSignals, setDispatchPageResolver, createWatchSession, collectProjectEntries, logWatchReady, destroyRuntime })=>{
|
|
4459
|
+
const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions, projectServers, v8Coverage, allTestFiles, projectRuntimeConfigs, hostOptions, watchState, isWatchMode, createDispatchRouter, handlers: { handleFatal, handleTestFileComplete }, watchSignals, setDispatchPageResolver, createWatchSession, collectProjectEntries, logWatchReady, destroyRuntime })=>{
|
|
4057
4460
|
const viewportByProject = mapViewportByProject(projectRuntimeConfigs);
|
|
4461
|
+
const projectRootByName = new Map(projectRuntimeConfigs.map((project)=>[
|
|
4462
|
+
project.name,
|
|
4463
|
+
project.projectRoot
|
|
4464
|
+
]));
|
|
4058
4465
|
const runLifecycle = new RunSessionLifecycle();
|
|
4059
4466
|
const sessionRegistry = new RunnerSessionRegistry();
|
|
4060
4467
|
setDispatchPageResolver((target)=>({
|
|
@@ -4075,12 +4482,32 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4075
4482
|
}
|
|
4076
4483
|
});
|
|
4077
4484
|
};
|
|
4485
|
+
const collectRunCoverage = async (run)=>{
|
|
4486
|
+
const results = await Promise.allSettled(Array.from(run.coverageCollectors, (collect)=>collect()));
|
|
4487
|
+
for (const result of results)if ('rejected' === result.status) {
|
|
4488
|
+
const error = toError(result.reason);
|
|
4489
|
+
await handleFatal({
|
|
4490
|
+
message: error.message,
|
|
4491
|
+
stack: error.stack
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4078
4495
|
const dispatchRouter = createDispatchRouter({
|
|
4079
4496
|
isRunTokenStale: (runToken)=>runLifecycle.isTokenStale(runToken),
|
|
4080
4497
|
onStale: (request)=>{
|
|
4081
4498
|
if ("runner" === request.namespace) logger.debug(`[Headless] Dropped stale message "${request.method}" for ${request.target?.testFile ?? 'unknown'}`);
|
|
4082
4499
|
}
|
|
4083
4500
|
});
|
|
4501
|
+
const fileCleanupHandlers = new Map();
|
|
4502
|
+
dispatchRouter.register(DISPATCH_NAMESPACE_FILE_CLEANUP, async (request)=>{
|
|
4503
|
+
const sessionId = request.target?.sessionId;
|
|
4504
|
+
if (!sessionId) throw new Error('File cleanup dispatch is missing a browser session.');
|
|
4505
|
+
const handler = fileCleanupHandlers.get(sessionId);
|
|
4506
|
+
if (!handler) return;
|
|
4507
|
+
const payload = request.args;
|
|
4508
|
+
if ('start' === request.method) handler.start(payload.result);
|
|
4509
|
+
else if ('end' === request.method) handler.end();
|
|
4510
|
+
});
|
|
4084
4511
|
const dispatchRunnerMessage = async (run, file, sessionId, message)=>{
|
|
4085
4512
|
const response = await dispatchRouter.dispatch({
|
|
4086
4513
|
requestId: nextDispatchRequestId("runner"),
|
|
@@ -4097,7 +4524,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4097
4524
|
if (response.stale) return;
|
|
4098
4525
|
if (response.error) throw new Error(response.error);
|
|
4099
4526
|
};
|
|
4100
|
-
const runSingleFile = async (run, file)=>{
|
|
4527
|
+
const runSingleFile = async (run, file, rawCoverage)=>{
|
|
4101
4528
|
if (run.cancelled || runLifecycle.isTokenStale(run.token)) return;
|
|
4102
4529
|
const viewport = viewportByProject.get(file.projectName);
|
|
4103
4530
|
const browserContext = await browser.newContext({
|
|
@@ -4107,8 +4534,12 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4107
4534
|
run.contexts.add(browserContext);
|
|
4108
4535
|
let page = null;
|
|
4109
4536
|
let sessionId = null;
|
|
4537
|
+
let coverageCollection;
|
|
4110
4538
|
let settled = false;
|
|
4111
4539
|
let resolveDone = null;
|
|
4540
|
+
let cleanupTimer;
|
|
4541
|
+
let fileCleanupFinished = false;
|
|
4542
|
+
let provisionalResult;
|
|
4112
4543
|
const markDone = ()=>{
|
|
4113
4544
|
if (!settled) {
|
|
4114
4545
|
settled = true;
|
|
@@ -4124,8 +4555,18 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4124
4555
|
settled = true;
|
|
4125
4556
|
crashDeferred.resolve(reason);
|
|
4126
4557
|
};
|
|
4558
|
+
const collectCoverage = async ()=>{
|
|
4559
|
+
if (!page || !v8Coverage) return;
|
|
4560
|
+
if (!coverageCollection) coverageCollection = (async ()=>{
|
|
4561
|
+
const coverage = await v8Coverage.take(page, projectRootByName.get(file.projectName) ?? context.rootPath, file.projectName);
|
|
4562
|
+
if (coverage) rawCoverage.push(coverage);
|
|
4563
|
+
})();
|
|
4564
|
+
await coverageCollection;
|
|
4565
|
+
};
|
|
4127
4566
|
try {
|
|
4128
4567
|
page = await browserContext.newPage();
|
|
4568
|
+
await v8Coverage?.start(page);
|
|
4569
|
+
run.coverageCollectors.add(collectCoverage);
|
|
4129
4570
|
page.on('crash', ()=>onPageDead(`Browser page crashed while running ${file.testPath}.`));
|
|
4130
4571
|
page.on('close', ()=>onPageDead(`Browser page closed unexpectedly while running ${file.testPath}.`));
|
|
4131
4572
|
const session = sessionRegistry.register({
|
|
@@ -4137,13 +4578,61 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4137
4578
|
page
|
|
4138
4579
|
});
|
|
4139
4580
|
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
|
+
fileCleanupHandlers.set(session.id, {
|
|
4592
|
+
end: finishFileCleanup,
|
|
4593
|
+
start: (result)=>{
|
|
4594
|
+
if (fileCleanupFinished) return;
|
|
4595
|
+
provisionalResult = result;
|
|
4596
|
+
clearFileCleanupTimeout();
|
|
4597
|
+
cleanupTimer = setTimeout(()=>{
|
|
4598
|
+
(async ()=>{
|
|
4599
|
+
if (settled) return;
|
|
4600
|
+
settled = true;
|
|
4601
|
+
const message = `File fixture cleanup did not finish within ${FIXTURE_CLEANUP_TIMEOUT_MS}ms`;
|
|
4602
|
+
try {
|
|
4603
|
+
await collectCoverage();
|
|
4604
|
+
await handleTestFileComplete(createFileCleanupTimeoutResult({
|
|
4605
|
+
message,
|
|
4606
|
+
projectName: file.projectName,
|
|
4607
|
+
result: provisionalResult,
|
|
4608
|
+
testPath: file.testPath
|
|
4609
|
+
}));
|
|
4610
|
+
} catch (error) {
|
|
4611
|
+
const formatted = toError(error);
|
|
4612
|
+
await handleFatal({
|
|
4613
|
+
message: formatted.message,
|
|
4614
|
+
stack: formatted.stack
|
|
4615
|
+
});
|
|
4616
|
+
await collectRunCoverage(run);
|
|
4617
|
+
await cancelRun(run, false);
|
|
4618
|
+
} finally{
|
|
4619
|
+
resolveDone?.();
|
|
4620
|
+
}
|
|
4621
|
+
})();
|
|
4622
|
+
}, FIXTURE_CLEANUP_TIMEOUT_MS);
|
|
4623
|
+
}
|
|
4624
|
+
});
|
|
4140
4625
|
await attachHeadlessRunnerTransport(page, {
|
|
4141
|
-
onDispatchMessage: async (
|
|
4626
|
+
onDispatchMessage: async (envelope)=>{
|
|
4627
|
+
const message = envelope.message;
|
|
4142
4628
|
try {
|
|
4629
|
+
if (settled) return;
|
|
4630
|
+
if ('file-complete' === message.type || 'fatal' === message.type) await collectCoverage();
|
|
4143
4631
|
await dispatchRunnerMessage(run, file, session.id, message);
|
|
4144
|
-
if ('file-complete' === message.type
|
|
4632
|
+
if ('file-complete' === message.type) markDone();
|
|
4145
4633
|
else if ('fatal' === message.type) {
|
|
4146
4634
|
markDone();
|
|
4635
|
+
await collectRunCoverage(run);
|
|
4147
4636
|
await cancelRun(run, false);
|
|
4148
4637
|
}
|
|
4149
4638
|
} catch (error) {
|
|
@@ -4153,6 +4642,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4153
4642
|
stack: formatted.stack
|
|
4154
4643
|
});
|
|
4155
4644
|
markDone();
|
|
4645
|
+
await collectRunCoverage(run);
|
|
4156
4646
|
await cancelRun(run, false);
|
|
4157
4647
|
}
|
|
4158
4648
|
},
|
|
@@ -4196,6 +4686,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4196
4686
|
]);
|
|
4197
4687
|
if ('cancelled' === state.type) return;
|
|
4198
4688
|
if ('crash' === state.type && runLifecycle.isTokenActive(run.token) && !run.cancelled) {
|
|
4689
|
+
await collectRunCoverage(run);
|
|
4199
4690
|
await handleFatal({
|
|
4200
4691
|
message: state.reason
|
|
4201
4692
|
});
|
|
@@ -4203,6 +4694,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4203
4694
|
}
|
|
4204
4695
|
} catch (error) {
|
|
4205
4696
|
if (runLifecycle.isTokenActive(run.token) && !run.cancelled) {
|
|
4697
|
+
await collectRunCoverage(run);
|
|
4206
4698
|
const formatted = toError(error);
|
|
4207
4699
|
await handleFatal({
|
|
4208
4700
|
message: formatted.message,
|
|
@@ -4211,6 +4703,9 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4211
4703
|
await cancelRun(run, false);
|
|
4212
4704
|
}
|
|
4213
4705
|
} finally{
|
|
4706
|
+
if (cleanupTimer) clearTimeout(cleanupTimer);
|
|
4707
|
+
if (sessionId) fileCleanupHandlers.delete(sessionId);
|
|
4708
|
+
run.coverageCollectors.delete(collectCoverage);
|
|
4214
4709
|
const abandoned = run.cancelled || runLifecycle.isTokenStale(run.token);
|
|
4215
4710
|
const teardown = async ()=>{
|
|
4216
4711
|
if (page) try {
|
|
@@ -4233,12 +4728,14 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4233
4728
|
results: []
|
|
4234
4729
|
});
|
|
4235
4730
|
const runFilesWithPool = async (files)=>{
|
|
4236
|
-
|
|
4731
|
+
const rawCoverage = [];
|
|
4732
|
+
if (0 === files.length) return rawCoverage;
|
|
4237
4733
|
const previous = runLifecycle.activeSession;
|
|
4238
4734
|
if (previous) await cancelRun(previous);
|
|
4239
4735
|
const run = runLifecycle.createSession((token)=>({
|
|
4240
4736
|
...createRunSession(token),
|
|
4241
|
-
contexts: new Set()
|
|
4737
|
+
contexts: new Set(),
|
|
4738
|
+
coverageCollectors: new Set()
|
|
4242
4739
|
}));
|
|
4243
4740
|
const queue = [
|
|
4244
4741
|
...files
|
|
@@ -4257,7 +4754,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4257
4754
|
}
|
|
4258
4755
|
const next = queue.shift();
|
|
4259
4756
|
if (!next) return;
|
|
4260
|
-
await runSingleFile(run, next);
|
|
4757
|
+
await runSingleFile(run, next, rawCoverage);
|
|
4261
4758
|
}
|
|
4262
4759
|
};
|
|
4263
4760
|
run.done = Promise.all(Array.from({
|
|
@@ -4265,15 +4762,16 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4265
4762
|
}, ()=>worker())).then(()=>{});
|
|
4266
4763
|
await run.done;
|
|
4267
4764
|
runLifecycle.clearIfActive(run);
|
|
4765
|
+
return rawCoverage;
|
|
4268
4766
|
};
|
|
4269
4767
|
const testStart = Date.now();
|
|
4270
|
-
await runFilesWithPool(allTestFiles);
|
|
4768
|
+
const rawCoverage = await runFilesWithPool(allTestFiles);
|
|
4271
4769
|
const testTime = Date.now() - testStart;
|
|
4272
4770
|
let watchSession;
|
|
4273
4771
|
if (isWatchMode) {
|
|
4274
4772
|
const runScope = async (testPaths)=>{
|
|
4275
4773
|
const pathSet = new Set(testPaths.map((testPath)=>normalize(testPath)));
|
|
4276
|
-
|
|
4774
|
+
return runFilesWithPool(watchState.lastTestFiles.filter((file)=>pathSet.has(file.testPath)));
|
|
4277
4775
|
};
|
|
4278
4776
|
watchSignals.setInterrupt(async ()=>{
|
|
4279
4777
|
const active = runLifecycle.activeSession;
|
|
@@ -4290,30 +4788,10 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4290
4788
|
previousTestFiles: watchState.lastTestFiles,
|
|
4291
4789
|
affectedTestFiles: drainPendingAffectedTestFiles(watchState)
|
|
4292
4790
|
});
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
if (0 === rerunPlan.currentTestFiles.length) {
|
|
4298
|
-
logger.log(color.cyan('No browser test files remain after update.\n'));
|
|
4299
|
-
await watchSignals.signalInvalidation([]);
|
|
4300
|
-
return;
|
|
4301
|
-
}
|
|
4302
|
-
logger.log(color.cyan(`Test file set changed, re-running ${rerunPlan.currentTestFiles.length} file(s)...\n`));
|
|
4303
|
-
await watchSignals.signalInvalidation([
|
|
4304
|
-
...new Set(rerunPlan.currentTestFiles.map((file)=>file.testPath))
|
|
4305
|
-
]);
|
|
4306
|
-
return;
|
|
4307
|
-
}
|
|
4308
|
-
if (0 === rerunPlan.affectedTestFiles.length) {
|
|
4309
|
-
logger.log(color.cyan('No affected browser test files detected, skipping re-run.\n'));
|
|
4310
|
-
logWatchReady();
|
|
4311
|
-
return;
|
|
4312
|
-
}
|
|
4313
|
-
logger.log(color.cyan(`Re-running ${rerunPlan.affectedTestFiles.length} affected test file(s)...\n`));
|
|
4314
|
-
await watchSignals.signalInvalidation([
|
|
4315
|
-
...new Set(rerunPlan.affectedTestFiles.map((file)=>file.testPath))
|
|
4316
|
-
]);
|
|
4791
|
+
commitWatchFileSetUpdate(rerunPlan.fileSetUpdate, watchState, (deletedTestPaths)=>context.updateReporterResultState([], [], deletedTestPaths));
|
|
4792
|
+
logger.log(color.cyan(rerunPlan.decision.message));
|
|
4793
|
+
if ('idle' === rerunPlan.decision.kind) return void logWatchReady();
|
|
4794
|
+
await watchSignals.signalInvalidation('empty' === rerunPlan.decision.kind ? [] : rerunPlan.decision.testPaths);
|
|
4317
4795
|
});
|
|
4318
4796
|
watchSession = createWatchSession(runScope);
|
|
4319
4797
|
}
|
|
@@ -4323,6 +4801,7 @@ const createHeadlessScheduler = async ({ context, browser, browserLaunchOptions,
|
|
|
4323
4801
|
};
|
|
4324
4802
|
return {
|
|
4325
4803
|
testTime,
|
|
4804
|
+
rawCoverage,
|
|
4326
4805
|
watchSession,
|
|
4327
4806
|
close: closeHeadlessRuntime
|
|
4328
4807
|
};
|
|
@@ -4351,7 +4830,6 @@ const validateBrowserRpcRequest = (payload)=>{
|
|
|
4351
4830
|
const request = {
|
|
4352
4831
|
id: readString(payload, 'id', 'id'),
|
|
4353
4832
|
testPath: readString(payload, 'testPath', 'testPath'),
|
|
4354
|
-
runId: readString(payload, 'runId', 'runId'),
|
|
4355
4833
|
kind,
|
|
4356
4834
|
locator: parseBrowserLocatorIR(payload.locator, 'locator'),
|
|
4357
4835
|
method: readString(payload, 'method', 'method'),
|
|
@@ -4369,47 +4847,6 @@ const validateBrowserRpcRequest = (payload)=>{
|
|
|
4369
4847
|
}
|
|
4370
4848
|
return request;
|
|
4371
4849
|
};
|
|
4372
|
-
const normalizeJavaScriptUrl = (value, options)=>{
|
|
4373
|
-
try {
|
|
4374
|
-
const url = options?.origin ? new URL(value, options.origin) : new URL(value);
|
|
4375
|
-
if ('http:' !== url.protocol && 'https:' !== url.protocol) return null;
|
|
4376
|
-
url.search = '';
|
|
4377
|
-
url.hash = '';
|
|
4378
|
-
return url.toString();
|
|
4379
|
-
} catch {
|
|
4380
|
-
return null;
|
|
4381
|
-
}
|
|
4382
|
-
};
|
|
4383
|
-
const resolveInlineSourceMap = (code)=>{
|
|
4384
|
-
const converter = convert_source_map.fromSource(code);
|
|
4385
|
-
if (!converter) return null;
|
|
4386
|
-
return converter.toObject();
|
|
4387
|
-
};
|
|
4388
|
-
const fetchSourceMap = async (jsUrl, fetcher)=>{
|
|
4389
|
-
const jsResponse = await fetcher(jsUrl);
|
|
4390
|
-
if (!jsResponse.ok) return null;
|
|
4391
|
-
const code = await jsResponse.text();
|
|
4392
|
-
const inlineMap = resolveInlineSourceMap(code);
|
|
4393
|
-
if (inlineMap) return inlineMap;
|
|
4394
|
-
const mapResponse = await fetcher(`${jsUrl}.map`);
|
|
4395
|
-
if (!mapResponse.ok) return null;
|
|
4396
|
-
return await mapResponse.json();
|
|
4397
|
-
};
|
|
4398
|
-
const loadSourceMapWithCache = async ({ jsUrl, cache, force = false, origin, fetcher = fetch })=>{
|
|
4399
|
-
const normalizedUrl = normalizeJavaScriptUrl(jsUrl, {
|
|
4400
|
-
origin
|
|
4401
|
-
});
|
|
4402
|
-
if (!normalizedUrl) return null;
|
|
4403
|
-
if (!force && cache.has(normalizedUrl)) return cache.get(normalizedUrl) ?? null;
|
|
4404
|
-
try {
|
|
4405
|
-
const sourceMap = await fetchSourceMap(normalizedUrl, fetcher);
|
|
4406
|
-
cache.set(normalizedUrl, sourceMap);
|
|
4407
|
-
return sourceMap;
|
|
4408
|
-
} catch {
|
|
4409
|
-
cache.set(normalizedUrl, null);
|
|
4410
|
-
return null;
|
|
4411
|
-
}
|
|
4412
|
-
};
|
|
4413
4850
|
const watchContext = {
|
|
4414
4851
|
runtime: null,
|
|
4415
4852
|
cleanupRegistered: false,
|
|
@@ -4455,12 +4892,16 @@ const createWatchSignals = (onInvalidate)=>{
|
|
|
4455
4892
|
async signalInvalidation (fileFilters, claimScope) {
|
|
4456
4893
|
await interruptInFlightRun?.();
|
|
4457
4894
|
claimScope?.();
|
|
4458
|
-
|
|
4895
|
+
const cycle = Promise.resolve(onInvalidate?.({
|
|
4459
4896
|
isFirstBuild: false,
|
|
4460
4897
|
fileFilters
|
|
4461
|
-
})).
|
|
4898
|
+
})).then(()=>{}, (error)=>{
|
|
4462
4899
|
logger.error(color.red('Browser Mode watch cycle failed:'), error);
|
|
4463
4900
|
});
|
|
4901
|
+
signalledCycle = cycle;
|
|
4902
|
+
return {
|
|
4903
|
+
cycle
|
|
4904
|
+
};
|
|
4464
4905
|
},
|
|
4465
4906
|
async awaitSignalledCycle () {
|
|
4466
4907
|
await signalledCycle;
|
|
@@ -4490,6 +4931,21 @@ const runBrowserController = async (context, options)=>{
|
|
|
4490
4931
|
const browserProjects = options?.projects ?? getBrowserProjects(context);
|
|
4491
4932
|
const useHeadlessDirect = browserProjects.every((project)=>project.normalizedConfig.browser.headless);
|
|
4492
4933
|
const browserSourceMapCache = new Map();
|
|
4934
|
+
const browserCoverageResources = {
|
|
4935
|
+
assetFiles: new Map(),
|
|
4936
|
+
sourceMaps: new Map()
|
|
4937
|
+
};
|
|
4938
|
+
const loadBrowserCoverageResources = async (filenames, resource)=>{
|
|
4939
|
+
const store = browserCoverageResources[resource];
|
|
4940
|
+
const loaded = {};
|
|
4941
|
+
for (const filename of filenames){
|
|
4942
|
+
const value = store.get(filename);
|
|
4943
|
+
if (void 0 !== value) loaded[filename] = value;
|
|
4944
|
+
}
|
|
4945
|
+
return loaded;
|
|
4946
|
+
};
|
|
4947
|
+
const loadBrowserCoverageAssetFiles = (filenames)=>loadBrowserCoverageResources(filenames, 'assetFiles');
|
|
4948
|
+
const loadBrowserCoverageSourceMaps = (filenames)=>loadBrowserCoverageResources(filenames, 'sourceMaps');
|
|
4493
4949
|
const isHttpLikeFile = (file)=>/^https?:\/\//.test(file);
|
|
4494
4950
|
const resolveBrowserSourcemap = async (sourcePath)=>{
|
|
4495
4951
|
if (!isHttpLikeFile(sourcePath)) return {
|
|
@@ -4546,7 +5002,8 @@ const runBrowserController = async (context, options)=>{
|
|
|
4546
5002
|
}
|
|
4547
5003
|
};
|
|
4548
5004
|
const coverageConfig = browserProjects.find((project)=>project.normalizedConfig.coverage?.enabled)?.normalizedConfig.coverage;
|
|
4549
|
-
const coverageProvider = coverageConfig?.enabled ? await createCoverageProvider(coverageConfig, context.rootPath) : null;
|
|
5005
|
+
const coverageProvider = !filesOnly && 'list' !== context.command && coverageConfig?.enabled ? await createCoverageProvider(coverageConfig, context.rootPath) : null;
|
|
5006
|
+
const browserCoverageCapabilityError = filesOnly || 'list' === context.command || coverageConfig?.provider !== 'v8' || (browserProjects[0]?.normalizedConfig.browser.browser ?? 'chromium') !== 'chromium' || coverageProvider?.supportsBrowserCoverage === true ? void 0 : new Error("The installed @rstest/coverage-v8 provider does not support Chromium Browser Mode coverage. Upgrade @rstest/coverage-v8 to the version matching @rstest/core, or use coverage.provider: 'istanbul'.");
|
|
4550
5007
|
const containerDevServerEnv = process.env.RSTEST_CONTAINER_DEV_SERVER;
|
|
4551
5008
|
let containerDevServer;
|
|
4552
5009
|
let containerDistPath;
|
|
@@ -4609,12 +5066,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
4609
5066
|
freezeShardedEntries: options?.freezeShardedEntries,
|
|
4610
5067
|
tempDir,
|
|
4611
5068
|
isWatchMode,
|
|
4612
|
-
onTriggerRerun: isWatchMode ? async ()=>{
|
|
4613
|
-
await watchSignals.runDispatchRerun();
|
|
4614
|
-
} : void 0,
|
|
4615
5069
|
containerDistPath,
|
|
4616
5070
|
containerDevServer,
|
|
4617
|
-
skipProviderLaunch: filesOnly,
|
|
5071
|
+
skipProviderLaunch: filesOnly || Boolean(browserCoverageCapabilityError),
|
|
4618
5072
|
appliedModifyRstestConfigEnvironments: options?.appliedModifyRstestConfigEnvironments
|
|
4619
5073
|
});
|
|
4620
5074
|
} catch (error) {
|
|
@@ -4625,13 +5079,18 @@ const runBrowserController = async (context, options)=>{
|
|
|
4625
5079
|
}).catch(()=>{});
|
|
4626
5080
|
});
|
|
4627
5081
|
}
|
|
4628
|
-
if (isWatchMode && !filesOnly) {
|
|
5082
|
+
if (isWatchMode && !filesOnly && !browserCoverageCapabilityError) {
|
|
4629
5083
|
watchContext.runtime = runtime;
|
|
4630
5084
|
registerWatchCleanup(context.embedded);
|
|
4631
5085
|
}
|
|
4632
5086
|
}
|
|
4633
5087
|
const watchState = runtime.watchState;
|
|
4634
|
-
if (isWatchMode)
|
|
5088
|
+
if (isWatchMode) {
|
|
5089
|
+
watchState.lastTestFiles = collectWatchTestFiles(projectEntries);
|
|
5090
|
+
watchState.triggerRerun = async ()=>{
|
|
5091
|
+
await watchSignals.runDispatchRerun();
|
|
5092
|
+
};
|
|
5093
|
+
}
|
|
4635
5094
|
const seedPendingRerun = (testPaths)=>{
|
|
4636
5095
|
const wanted = testPaths ? new Set(testPaths.map((testPath)=>normalize(testPath))) : null;
|
|
4637
5096
|
let seeded = 0;
|
|
@@ -4644,7 +5103,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4644
5103
|
}
|
|
4645
5104
|
return seeded;
|
|
4646
5105
|
};
|
|
4647
|
-
const buildRerunOutcome = ({ rerunTestPaths, testTime, unhandledErrors })=>{
|
|
5106
|
+
const buildRerunOutcome = ({ rerunTestPaths, testTime, rawCoverage, unhandledErrors })=>{
|
|
4648
5107
|
const rerunPathSet = new Set(rerunTestPaths);
|
|
4649
5108
|
const rerunResults = context.reporterResults.results.filter((result)=>rerunPathSet.has(result.testPath));
|
|
4650
5109
|
const coverageMap = buildBrowserCoverageMap(rerunResults, coverageProvider);
|
|
@@ -4657,8 +5116,11 @@ const runBrowserController = async (context, options)=>{
|
|
|
4657
5116
|
buildTime: drainPendingBuildTime(watchState),
|
|
4658
5117
|
testTime
|
|
4659
5118
|
},
|
|
4660
|
-
coverage: coverageMap?.files().length ? {
|
|
4661
|
-
map: coverageMap
|
|
5119
|
+
coverage: coverageMap?.files().length || rawCoverage.length ? {
|
|
5120
|
+
map: coverageMap?.toJSON(),
|
|
5121
|
+
raw: rawCoverage,
|
|
5122
|
+
loadAssetFiles: loadBrowserCoverageAssetFiles,
|
|
5123
|
+
loadSourceMaps: loadBrowserCoverageSourceMaps
|
|
4662
5124
|
} : void 0,
|
|
4663
5125
|
resolveSourcemap: resolveBrowserSourcemap
|
|
4664
5126
|
};
|
|
@@ -4666,17 +5128,19 @@ const runBrowserController = async (context, options)=>{
|
|
|
4666
5128
|
const createWatchSession = (execute)=>({
|
|
4667
5129
|
runCycle: async (testPaths)=>{
|
|
4668
5130
|
const rerunStartTime = Date.now();
|
|
4669
|
-
|
|
5131
|
+
fatalErrorRef.current = null;
|
|
4670
5132
|
let rerunError;
|
|
5133
|
+
let rawCoverage = [];
|
|
4671
5134
|
try {
|
|
4672
|
-
await execute(testPaths);
|
|
5135
|
+
rawCoverage = await execute(testPaths);
|
|
4673
5136
|
} catch (error) {
|
|
4674
5137
|
rerunError = toError(error);
|
|
4675
5138
|
}
|
|
4676
|
-
const rerunFatalError = fatalErrorRef.current
|
|
5139
|
+
const rerunFatalError = fatalErrorRef.current ?? void 0;
|
|
4677
5140
|
return buildRerunOutcome({
|
|
4678
5141
|
rerunTestPaths: testPaths,
|
|
4679
5142
|
testTime: Math.max(0, Date.now() - rerunStartTime),
|
|
5143
|
+
rawCoverage,
|
|
4680
5144
|
unhandledErrors: rerunError ? [
|
|
4681
5145
|
rerunError
|
|
4682
5146
|
] : rerunFatalError ? [
|
|
@@ -4712,6 +5176,10 @@ const runBrowserController = async (context, options)=>{
|
|
|
4712
5176
|
await destroyBrowserRuntime(runtime);
|
|
4713
5177
|
return allowEmptyRun ? createEmptyRunResult() : void 0;
|
|
4714
5178
|
}
|
|
5179
|
+
if (browserCoverageCapabilityError) {
|
|
5180
|
+
await destroyBrowserRuntime(runtime);
|
|
5181
|
+
return failWithError(browserCoverageCapabilityError);
|
|
5182
|
+
}
|
|
4715
5183
|
const { browser, browserLaunchOptions, wsPort } = runtime;
|
|
4716
5184
|
const allTestFiles = projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
4717
5185
|
testPath: normalize(testPath),
|
|
@@ -4734,17 +5202,18 @@ const runBrowserController = async (context, options)=>{
|
|
|
4734
5202
|
name,
|
|
4735
5203
|
`http://localhost:${server.port}`
|
|
4736
5204
|
]));
|
|
5205
|
+
const containerRunnerUrl = `http://localhost:${runtime.containerServer.port}`;
|
|
4737
5206
|
const hostOptions = {
|
|
4738
5207
|
rootPath: normalize(context.rootPath),
|
|
4739
5208
|
projects: projectRuntimeConfigs,
|
|
4740
5209
|
snapshot: {
|
|
4741
5210
|
updateSnapshot: options?.updateSnapshot ?? context.snapshotManager.options.updateSnapshot
|
|
4742
5211
|
},
|
|
4743
|
-
runnerUrl:
|
|
5212
|
+
runnerUrl: containerRunnerUrl,
|
|
4744
5213
|
projectRunnerUrls,
|
|
4745
5214
|
wsPort,
|
|
4746
5215
|
debug: isDebug(),
|
|
4747
|
-
rpcTimeout:
|
|
5216
|
+
rpcTimeout: -1
|
|
4748
5217
|
};
|
|
4749
5218
|
const browserProviderProjects = browserProjects.map((project)=>({
|
|
4750
5219
|
rootPath: normalize(project.rootPath),
|
|
@@ -4752,6 +5221,22 @@ const runBrowserController = async (context, options)=>{
|
|
|
4752
5221
|
}));
|
|
4753
5222
|
const implementationByProvider = new Map();
|
|
4754
5223
|
for (const browserProject of browserProviderProjects)if (!implementationByProvider.has(browserProject.provider)) implementationByProvider.set(browserProject.provider, getBrowserProviderImplementation(browserProject.provider));
|
|
5224
|
+
const browserName = browserLaunchOptions.browser ?? 'chromium';
|
|
5225
|
+
const v8CoverageCollector = coverageConfig?.provider === 'v8' ? implementationByProvider.get(browserLaunchOptions.provider)?.createV8CoverageCollector?.({
|
|
5226
|
+
browserName
|
|
5227
|
+
}) ?? null : null;
|
|
5228
|
+
const v8Coverage = v8CoverageCollector ? {
|
|
5229
|
+
start: v8CoverageCollector.start,
|
|
5230
|
+
take: (page, projectRoot, projectName)=>takeBrowserV8Coverage({
|
|
5231
|
+
collector: v8CoverageCollector,
|
|
5232
|
+
fetchTimeout: maxTestTimeoutForRpc,
|
|
5233
|
+
page,
|
|
5234
|
+
projectUrl: projectRunnerUrls[projectName ?? ''] ?? containerRunnerUrl,
|
|
5235
|
+
rootPath: normalize(projectRoot),
|
|
5236
|
+
sourceMapCache: browserSourceMapCache,
|
|
5237
|
+
resourceStore: browserCoverageResources
|
|
5238
|
+
})
|
|
5239
|
+
} : void 0;
|
|
4755
5240
|
let resolveDispatchPages = ()=>({});
|
|
4756
5241
|
const setDispatchPageResolver = (resolver)=>{
|
|
4757
5242
|
resolveDispatchPages = resolver;
|
|
@@ -4801,14 +5286,22 @@ const runBrowserController = async (context, options)=>{
|
|
|
4801
5286
|
if (!sink) throw new Error(`No runner event sink for project "${projectName}"`);
|
|
4802
5287
|
return sink;
|
|
4803
5288
|
};
|
|
4804
|
-
const
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
5289
|
+
const silentConsoleControllers = new Map(browserProjects.map((project)=>[
|
|
5290
|
+
project.name,
|
|
5291
|
+
createSilentConsoleController({
|
|
5292
|
+
runtimeConfig: {
|
|
5293
|
+
silent: project.normalizedConfig.silent,
|
|
5294
|
+
disableConsoleIntercept: project.normalizedConfig.disableConsoleIntercept
|
|
5295
|
+
},
|
|
5296
|
+
emitInterceptedLog: (log)=>sinkForProjectName(log.project).onConsoleLog(log),
|
|
5297
|
+
writeOriginalLog: ()=>{}
|
|
5298
|
+
})
|
|
5299
|
+
]));
|
|
5300
|
+
const silentConsoleControllerForProjectName = (projectName)=>{
|
|
5301
|
+
const controller = silentConsoleControllers.get(projectName);
|
|
5302
|
+
if (!controller) throw new Error(`No silent console controller for project "${projectName}"`);
|
|
5303
|
+
return controller;
|
|
5304
|
+
};
|
|
4812
5305
|
const snapshotRpcMethods = {
|
|
4813
5306
|
async resolveSnapshotPath (testPath) {
|
|
4814
5307
|
return resolveSnapshotPathDefault(testPath, context.normalizedConfig.resolveSnapshotPath);
|
|
@@ -4863,7 +5356,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4863
5356
|
const handleTestSuiteResult = async (payload)=>{
|
|
4864
5357
|
phaseTrackers?.get(trackerKey(payload.project, payload.testPath))?.recordSuiteResult(payload);
|
|
4865
5358
|
await sinkForProjectName(payload.project).onTestSuiteResult(payload);
|
|
4866
|
-
|
|
5359
|
+
silentConsoleControllerForProjectName(payload.project).flushBufferedLogsForTask({
|
|
4867
5360
|
taskId: payload.testId,
|
|
4868
5361
|
status: payload.status,
|
|
4869
5362
|
taskParentNames: payload.parentNames,
|
|
@@ -4879,7 +5372,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4879
5372
|
caseResults.push(payload);
|
|
4880
5373
|
phaseTrackers?.get(trackerKey(payload.project, payload.testPath))?.recordCaseResult(payload);
|
|
4881
5374
|
await sinkForProjectName(payload.project).onTestCaseResult(payload);
|
|
4882
|
-
|
|
5375
|
+
silentConsoleControllerForProjectName(payload.project).flushBufferedLogsForTask({
|
|
4883
5376
|
taskId: payload.testId,
|
|
4884
5377
|
status: payload.status,
|
|
4885
5378
|
taskParentNames: payload.parentNames,
|
|
@@ -4902,7 +5395,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4902
5395
|
phaseTrackers.delete(key);
|
|
4903
5396
|
}
|
|
4904
5397
|
}
|
|
4905
|
-
|
|
5398
|
+
silentConsoleControllerForProjectName(payload.project).flushBufferedLogsForTask({
|
|
4906
5399
|
taskId: payload.testId,
|
|
4907
5400
|
status: payload.status,
|
|
4908
5401
|
taskParentNames: payload.parentNames,
|
|
@@ -4924,7 +5417,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4924
5417
|
type: payload.type,
|
|
4925
5418
|
trace: payload.trace
|
|
4926
5419
|
};
|
|
4927
|
-
|
|
5420
|
+
silentConsoleControllerForProjectName(payload.projectName).onConsoleLog(log);
|
|
4928
5421
|
};
|
|
4929
5422
|
const handleFatal = async (payload)=>{
|
|
4930
5423
|
const error = new Error(payload.message);
|
|
@@ -4971,6 +5464,10 @@ const runBrowserController = async (context, options)=>{
|
|
|
4971
5464
|
context,
|
|
4972
5465
|
allTestFiles,
|
|
4973
5466
|
hostOptions,
|
|
5467
|
+
projectRoots: new Map(projectRuntimeConfigs.map((project)=>[
|
|
5468
|
+
project.name,
|
|
5469
|
+
project.projectRoot
|
|
5470
|
+
])),
|
|
4974
5471
|
isWatchMode,
|
|
4975
5472
|
createDispatchRouter,
|
|
4976
5473
|
fatalErrorRef,
|
|
@@ -4981,11 +5478,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
4981
5478
|
logWatchReady: ()=>logWatchReadyMessage(context, enableCliShortcuts),
|
|
4982
5479
|
destroyRuntime: ()=>destroyBrowserRuntime(runtime)
|
|
4983
5480
|
};
|
|
4984
|
-
const { testTime, watchSession, close } = useHeadlessDirect ? await createHeadlessScheduler({
|
|
5481
|
+
const { testTime, rawCoverage, watchSession, close } = useHeadlessDirect ? await createHeadlessScheduler({
|
|
4985
5482
|
...schedulerDeps,
|
|
4986
5483
|
browser,
|
|
4987
5484
|
browserLaunchOptions,
|
|
4988
5485
|
projectServers: runtime.projectServers,
|
|
5486
|
+
v8Coverage,
|
|
4989
5487
|
projectRuntimeConfigs,
|
|
4990
5488
|
watchState,
|
|
4991
5489
|
handlers: {
|
|
@@ -4995,17 +5493,23 @@ const runBrowserController = async (context, options)=>{
|
|
|
4995
5493
|
}) : await createHeadedScheduler({
|
|
4996
5494
|
...schedulerDeps,
|
|
4997
5495
|
runtime,
|
|
5496
|
+
v8Coverage,
|
|
4998
5497
|
handlers: {
|
|
4999
|
-
|
|
5000
|
-
handleTestCaseResult,
|
|
5001
|
-
handleTestFileComplete,
|
|
5002
|
-
handleLog,
|
|
5003
|
-
handleFatal
|
|
5498
|
+
handleTestFileComplete
|
|
5004
5499
|
}
|
|
5005
5500
|
});
|
|
5006
|
-
|
|
5501
|
+
watchState.hooksEnabled = void 0 !== watchSession;
|
|
5502
|
+
if (fatalErrorRef.current) {
|
|
5503
|
+
const errorResult = await failWithError(fatalErrorRef.current, close);
|
|
5504
|
+
return {
|
|
5505
|
+
...errorResult,
|
|
5506
|
+
rawCoverage,
|
|
5507
|
+
loadAssetFiles: loadBrowserCoverageAssetFiles,
|
|
5508
|
+
loadSourceMaps: loadBrowserCoverageSourceMaps,
|
|
5509
|
+
watchSession
|
|
5510
|
+
};
|
|
5511
|
+
}
|
|
5007
5512
|
context.updateReporterResultState(reporterResults, caseResults);
|
|
5008
|
-
watchState.hooksEnabled = isWatchMode;
|
|
5009
5513
|
return {
|
|
5010
5514
|
results: reporterResults,
|
|
5011
5515
|
testResults: caseResults,
|
|
@@ -5015,6 +5519,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
5015
5519
|
testTime
|
|
5016
5520
|
},
|
|
5017
5521
|
hasFailure: reporterResults.some((result)=>'fail' === result.status),
|
|
5522
|
+
rawCoverage,
|
|
5523
|
+
loadAssetFiles: loadBrowserCoverageAssetFiles,
|
|
5524
|
+
loadSourceMaps: loadBrowserCoverageSourceMaps,
|
|
5018
5525
|
getSourcemap: getBrowserSourcemap,
|
|
5019
5526
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
5020
5527
|
close,
|
|
@@ -5083,7 +5590,6 @@ const listBrowserTests = async (context, options)=>{
|
|
|
5083
5590
|
})),
|
|
5084
5591
|
viewport: project.normalizedConfig.browser.viewport
|
|
5085
5592
|
}));
|
|
5086
|
-
const maxTestTimeoutForRpc = getMaxTestTimeoutForRpc(browserProjects);
|
|
5087
5593
|
const hostOptions = {
|
|
5088
5594
|
rootPath: normalize(context.rootPath),
|
|
5089
5595
|
projects: projectRuntimeConfigs,
|
|
@@ -5092,7 +5598,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
5092
5598
|
},
|
|
5093
5599
|
mode: 'collect',
|
|
5094
5600
|
debug: isDebug(),
|
|
5095
|
-
rpcTimeout:
|
|
5601
|
+
rpcTimeout: -1
|
|
5096
5602
|
};
|
|
5097
5603
|
runtime.setContainerOptions(hostOptions);
|
|
5098
5604
|
const browserContext = await browser.newContext({
|
|
@@ -5110,7 +5616,8 @@ const listBrowserTests = async (context, options)=>{
|
|
|
5110
5616
|
resolveCollect = resolve;
|
|
5111
5617
|
});
|
|
5112
5618
|
const page = await browserContext.newPage();
|
|
5113
|
-
await page.exposeFunction(DISPATCH_MESSAGE_TYPE, (
|
|
5619
|
+
await page.exposeFunction(DISPATCH_MESSAGE_TYPE, (envelope)=>{
|
|
5620
|
+
const message = envelope.message;
|
|
5114
5621
|
switch(message.type){
|
|
5115
5622
|
case 'collect-result':
|
|
5116
5623
|
{
|
|
@@ -5229,9 +5736,12 @@ async function createBrowserExecutor(context, options) {
|
|
|
5229
5736
|
buildTime: result.duration.buildTime,
|
|
5230
5737
|
testTime: result.duration.testTime
|
|
5231
5738
|
},
|
|
5232
|
-
coverage: {
|
|
5233
|
-
map: map?.toJSON()
|
|
5234
|
-
|
|
5739
|
+
coverage: map?.files().length || result.rawCoverage?.length ? {
|
|
5740
|
+
map: map?.toJSON(),
|
|
5741
|
+
raw: result.rawCoverage,
|
|
5742
|
+
loadAssetFiles: result.loadAssetFiles,
|
|
5743
|
+
loadSourceMaps: result.loadSourceMaps
|
|
5744
|
+
} : void 0,
|
|
5235
5745
|
resolveSourcemap: result.resolveSourcemap
|
|
5236
5746
|
};
|
|
5237
5747
|
};
|