@rstest/browser 0.11.3 → 0.11.4
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/js/{949.7d935e2ac9.js → 243.a8eed2b9e7.js} +223 -223
- package/dist/browser-container/container-static/js/243.a8eed2b9e7.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.492a6c2f8b.js → index.84aaafaf21.js} +78 -73
- package/dist/browser-container/index.html +1 -1
- package/dist/index.js +232 -139
- package/package.json +7 -6
- package/src/browserExecutor.ts +5 -3
- package/src/configValidation.ts +6 -0
- package/src/hostController.ts +426 -116
- package/dist/browser-container/container-static/js/949.7d935e2ac9.js.LICENSE.txt +0 -1
- package/dist/watchCliShortcuts.d.ts +0 -6
- package/src/AGENTS.md +0 -150
- package/src/client/AGENTS.md +0 -89
- package/src/watchCliShortcuts.ts +0 -77
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_TEST_TIMEOUT, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, applyWebMockRspackConfig, browserIgnoredRuntimeConfigKeys, buildBrowserCoverageMap, color, createCoverageProvider, createRunnerEventSink, createSilentConsoleController, getNoTestFilesMessage, getNumCpus as browser_getNumCpus, getPrettyConsoleName, getSetupFiles, getTestEntries, hasUserRstestConfigPlugins, importMetaRstestDefine, initModifyRstestConfigHooks, isDebug, isTTY, loadCoverageProvider, logger, pluginMockRuntime, prepareWatchRerunState, projectRuntimeConfig, resolveProjectBuildCache, resolveShardedEntries, resolveSnapshotPathDefault, resolveWorkerCount, rsbuild, serializableConfig } from "@rstest/core/internal/browser";
|
|
1
|
+
import { DEFAULT_TEST_TIMEOUT, FATAL_SIGNALS, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, applyWatchInvalidation, applyWebMockRspackConfig, browserIgnoredRuntimeConfigKeys, buildBrowserCoverageMap, color, createCoverageProvider, createRunnerEventSink, createSilentConsoleController, finalizeRunCycle, getNoTestFilesMessage, getNumCpus as browser_getNumCpus, getPrettyConsoleName, getSetupFiles, getTestEntries, hasUserRstestConfigPlugins, importMetaRstestDefine, initModifyRstestConfigHooks, isDebug, isTTY, loadCoverageProvider, logWatchReadyMessage, logger, pluginMockRuntime, prepareWatchRerunState, projectRuntimeConfig, resolveProjectBuildCache, resolveShardedEntries, resolveSnapshotPathDefault, resolveWorkerCount, rsbuild, serializableConfig } from "@rstest/core/internal/browser";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import promises from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -2054,6 +2054,11 @@ const ignoredKeyWarnings = {
|
|
|
2054
2054
|
message: ()=>"Ignoring isolate: false in browser mode: each test file still runs in a fresh context.",
|
|
2055
2055
|
browserOnly: true
|
|
2056
2056
|
},
|
|
2057
|
+
federation: {
|
|
2058
|
+
isNonDefault: (config)=>true === config.federation,
|
|
2059
|
+
message: ()=>'Ignoring federation in browser mode: it only applies to the Node runner.',
|
|
2060
|
+
browserOnly: true
|
|
2061
|
+
},
|
|
2057
2062
|
detectAsyncLeaks: {
|
|
2058
2063
|
isNonDefault: (config)=>true === config.detectAsyncLeaks,
|
|
2059
2064
|
message: ()=>"Ignoring detectAsyncLeaks in browser mode: it relies on node async_hooks."
|
|
@@ -2835,44 +2840,6 @@ const loadSourceMapWithCache = async ({ jsUrl, cache, force = false, origin, fet
|
|
|
2835
2840
|
return null;
|
|
2836
2841
|
}
|
|
2837
2842
|
};
|
|
2838
|
-
const isBrowserWatchCliShortcutsEnabled = ()=>isTTY('stdin');
|
|
2839
|
-
const getBrowserWatchCliShortcutsHintMessage = ()=>` ${color.dim('press')} ${color.bold('q')} ${color.dim('to quit')}\n`;
|
|
2840
|
-
const logBrowserWatchReadyMessage = (enableCliShortcuts)=>{
|
|
2841
|
-
logger.log(color.green(' Waiting for file changes...'));
|
|
2842
|
-
if (enableCliShortcuts) logger.log(getBrowserWatchCliShortcutsHintMessage());
|
|
2843
|
-
};
|
|
2844
|
-
async function setupBrowserWatchCliShortcuts({ close }) {
|
|
2845
|
-
const { emitKeypressEvents } = await import("node:readline");
|
|
2846
|
-
emitKeypressEvents(process.stdin);
|
|
2847
|
-
process.stdin.setRawMode(true);
|
|
2848
|
-
process.stdin.resume();
|
|
2849
|
-
process.stdin.setEncoding('utf8');
|
|
2850
|
-
let isClosing = false;
|
|
2851
|
-
const handleKeypress = (str, key)=>{
|
|
2852
|
-
if (key.ctrl && 'c' === key.name) return void process.kill(process.pid, 'SIGINT');
|
|
2853
|
-
if (key.ctrl && 'z' === key.name) {
|
|
2854
|
-
if ('win32' !== process.platform) process.kill(process.pid, 'SIGTSTP');
|
|
2855
|
-
return;
|
|
2856
|
-
}
|
|
2857
|
-
if ('q' !== str || isClosing) return;
|
|
2858
|
-
isClosing = true;
|
|
2859
|
-
(async ()=>{
|
|
2860
|
-
try {
|
|
2861
|
-
await close();
|
|
2862
|
-
} finally{
|
|
2863
|
-
process.exit(0);
|
|
2864
|
-
}
|
|
2865
|
-
})();
|
|
2866
|
-
};
|
|
2867
|
-
process.stdin.on('keypress', handleKeypress);
|
|
2868
|
-
return ()=>{
|
|
2869
|
-
try {
|
|
2870
|
-
process.stdin.setRawMode(false);
|
|
2871
|
-
process.stdin.pause();
|
|
2872
|
-
} catch {}
|
|
2873
|
-
process.stdin.off('keypress', handleKeypress);
|
|
2874
|
-
};
|
|
2875
|
-
}
|
|
2876
2843
|
const serializeTestFiles = (files)=>JSON.stringify(files.map((f)=>`${f.projectName}:${f.testPath}`).sort());
|
|
2877
2844
|
const normalizeTestFiles = (files)=>files.map((file)=>({
|
|
2878
2845
|
...file,
|
|
@@ -3004,6 +2971,9 @@ class ContainerRpcManager {
|
|
|
3004
2971
|
async notifyTestFileUpdate(files) {
|
|
3005
2972
|
await this.rpc?.onTestFileUpdate(files);
|
|
3006
2973
|
}
|
|
2974
|
+
async updateHostConfig(config) {
|
|
2975
|
+
await this.rpc?.onHostConfigUpdate(config);
|
|
2976
|
+
}
|
|
3007
2977
|
async reloadTestFile(testFile, testNamePattern) {
|
|
3008
2978
|
logger.debug(`[Browser UI] reloadTestFile called, rpc: ${this.rpc ? 'exists' : 'null'}, ws: ${this.ws ? 'exists' : 'null'}`);
|
|
3009
2979
|
if (!this.rpc) throw new Error('Browser UI RPC not available for reloadTestFile');
|
|
@@ -3011,15 +2981,29 @@ class ContainerRpcManager {
|
|
|
3011
2981
|
return this.rpc.reloadTestFile(testFile, testNamePattern);
|
|
3012
2982
|
}
|
|
3013
2983
|
}
|
|
2984
|
+
const createBrowserWatchState = ()=>({
|
|
2985
|
+
lastTestFiles: [],
|
|
2986
|
+
hooksEnabled: false,
|
|
2987
|
+
invalidation: new Map(),
|
|
2988
|
+
pendingAffectedTestFiles: new Map(),
|
|
2989
|
+
compileStartTimes: new Map(),
|
|
2990
|
+
pendingBuildTimeMs: 0
|
|
2991
|
+
});
|
|
2992
|
+
const drainPendingBuildTime = (watchState)=>{
|
|
2993
|
+
const buildTime = watchState.pendingBuildTimeMs;
|
|
2994
|
+
watchState.pendingBuildTimeMs = 0;
|
|
2995
|
+
return buildTime;
|
|
2996
|
+
};
|
|
2997
|
+
const drainPendingAffectedTestFiles = (watchState)=>{
|
|
2998
|
+
const affected = new Set();
|
|
2999
|
+
for (const files of watchState.pendingAffectedTestFiles.values())for (const file of files)affected.add(file);
|
|
3000
|
+
watchState.pendingAffectedTestFiles.clear();
|
|
3001
|
+
return Array.from(affected);
|
|
3002
|
+
};
|
|
3014
3003
|
const watchContext = {
|
|
3015
3004
|
runtime: null,
|
|
3016
|
-
lastTestFiles: [],
|
|
3017
|
-
hooksEnabled: false,
|
|
3018
3005
|
cleanupRegistered: false,
|
|
3019
|
-
cleanupPromise: null
|
|
3020
|
-
closeCliShortcuts: null,
|
|
3021
|
-
chunkHashes: new Map(),
|
|
3022
|
-
affectedTestFiles: []
|
|
3006
|
+
cleanupPromise: null
|
|
3023
3007
|
};
|
|
3024
3008
|
const resolveViewport = (viewport)=>{
|
|
3025
3009
|
if (!viewport) return null;
|
|
@@ -3202,25 +3186,42 @@ const getChunkKey = (chunk)=>{
|
|
|
3202
3186
|
if (chunk.files && chunk.files.length > 0) return chunk.files[0];
|
|
3203
3187
|
return null;
|
|
3204
3188
|
};
|
|
3205
|
-
const getAffectedTestFiles = (chunks, entryTestFiles)=>{
|
|
3206
|
-
|
|
3207
|
-
const
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3189
|
+
const getAffectedTestFiles = ({ chunks, entryTestFiles, setupFiles, state })=>{
|
|
3190
|
+
const entryHashes = new Map();
|
|
3191
|
+
const setupHashes = new Map();
|
|
3192
|
+
const recordChunk = (snapshot, entryPath, chunkKey, hash)=>{
|
|
3193
|
+
const record = snapshot.get(entryPath) ?? {};
|
|
3194
|
+
record[chunkKey] = hash;
|
|
3195
|
+
snapshot.set(entryPath, record);
|
|
3196
|
+
};
|
|
3197
|
+
for (const chunk of chunks || []){
|
|
3210
3198
|
if (!chunk.hash) continue;
|
|
3211
|
-
const testFile = findTestFileInModules(chunk.modules, entryTestFiles);
|
|
3212
|
-
if (!testFile) continue;
|
|
3213
3199
|
const chunkKey = getChunkKey(chunk);
|
|
3214
3200
|
if (!chunkKey) continue;
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
logger.debug(`[Watch] Chunk hash changed for ${chunkKey}: ${prevHash} -> ${chunk.hash} (test: ${testFile})`);
|
|
3201
|
+
const testFile = findTestFileInModules(chunk.modules, entryTestFiles);
|
|
3202
|
+
if (testFile) {
|
|
3203
|
+
recordChunk(entryHashes, testFile, chunkKey, chunk.hash);
|
|
3204
|
+
continue;
|
|
3220
3205
|
}
|
|
3206
|
+
const setupFile = findTestFileInModules(chunk.modules, setupFiles);
|
|
3207
|
+
if (setupFile) recordChunk(setupHashes, setupFile, chunkKey, chunk.hash);
|
|
3221
3208
|
}
|
|
3222
|
-
|
|
3223
|
-
|
|
3209
|
+
const seedFirstSeen = (baseline, current)=>{
|
|
3210
|
+
if (!baseline) return;
|
|
3211
|
+
for (const [entryPath, record] of current)if (!baseline.has(entryPath)) baseline.set(entryPath, record);
|
|
3212
|
+
};
|
|
3213
|
+
seedFirstSeen(state.entryHashes, entryHashes);
|
|
3214
|
+
seedFirstSeen(state.setupHashes, setupHashes);
|
|
3215
|
+
const outcome = applyWatchInvalidation(state, {
|
|
3216
|
+
entryHashes,
|
|
3217
|
+
setupHashes
|
|
3218
|
+
});
|
|
3219
|
+
if (outcome.rerunAll) {
|
|
3220
|
+
logger.debug('[Watch] Setup file changed, re-running all test files of the project');
|
|
3221
|
+
return Array.from(entryTestFiles);
|
|
3222
|
+
}
|
|
3223
|
+
for (const affected of outcome.affectedPaths)logger.debug(`[Watch] Chunk hash changed for test: ${affected}`);
|
|
3224
|
+
return outcome.affectedPaths;
|
|
3224
3225
|
};
|
|
3225
3226
|
const getBrowserProjects = (context)=>context.projects.filter((project)=>project.normalizedConfig.browser.enabled);
|
|
3226
3227
|
const getBrowserRsbuildEnvironmentConfig = (project)=>({
|
|
@@ -3426,27 +3427,22 @@ const destroyBrowserRuntime = async (runtime)=>{
|
|
|
3426
3427
|
const cleanupWatchRuntime = ()=>{
|
|
3427
3428
|
if (watchContext.cleanupPromise) return watchContext.cleanupPromise;
|
|
3428
3429
|
watchContext.cleanupPromise = (async ()=>{
|
|
3429
|
-
watchContext.closeCliShortcuts?.();
|
|
3430
|
-
watchContext.closeCliShortcuts = null;
|
|
3431
3430
|
if (!watchContext.runtime) return;
|
|
3432
3431
|
await destroyBrowserRuntime(watchContext.runtime);
|
|
3433
3432
|
watchContext.runtime = null;
|
|
3434
3433
|
})();
|
|
3435
3434
|
return watchContext.cleanupPromise;
|
|
3436
3435
|
};
|
|
3437
|
-
const registerWatchCleanup = ()=>{
|
|
3436
|
+
const registerWatchCleanup = (embedded)=>{
|
|
3438
3437
|
if (watchContext.cleanupRegistered) return;
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
'SIGTSTP'
|
|
3443
|
-
])process.once(signal, ()=>{
|
|
3438
|
+
watchContext.cleanupRegistered = true;
|
|
3439
|
+
if (embedded) return;
|
|
3440
|
+
for (const signal of FATAL_SIGNALS)process.once(signal, ()=>{
|
|
3444
3441
|
cleanupWatchRuntime();
|
|
3445
3442
|
});
|
|
3446
3443
|
process.once('exit', ()=>{
|
|
3447
3444
|
cleanupWatchRuntime();
|
|
3448
3445
|
});
|
|
3449
|
-
watchContext.cleanupRegistered = true;
|
|
3450
3446
|
};
|
|
3451
3447
|
const createBrowserRuntime = async ({ context, projectEntries: initialProjectEntries, browserProjects, shardedEntries, freezeShardedEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless, skipProviderLaunch, appliedModifyRstestConfigEnvironments })=>{
|
|
3452
3448
|
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
@@ -3459,6 +3455,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3459
3455
|
};
|
|
3460
3456
|
let browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3461
3457
|
let projectEntries = initialProjectEntries;
|
|
3458
|
+
const watchState = createBrowserWatchState();
|
|
3462
3459
|
const manifestModules = [];
|
|
3463
3460
|
const createRuntimeWithoutProvider = ()=>{
|
|
3464
3461
|
const firstProject = browserProjects[0];
|
|
@@ -3481,7 +3478,8 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3481
3478
|
setContainerOptions,
|
|
3482
3479
|
dispatchHandlers,
|
|
3483
3480
|
wss: void 0,
|
|
3484
|
-
projectEntries
|
|
3481
|
+
projectEntries,
|
|
3482
|
+
watchState
|
|
3485
3483
|
};
|
|
3486
3484
|
};
|
|
3487
3485
|
const getProjectEntry = (project)=>projectEntries.find((item)=>item.project.environmentName === project.environmentName);
|
|
@@ -3644,9 +3642,19 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3644
3642
|
target: 'web',
|
|
3645
3643
|
sourceMap: {
|
|
3646
3644
|
js: 'source-map'
|
|
3645
|
+
},
|
|
3646
|
+
distPath: {
|
|
3647
|
+
root: join(tempDir, 'server', toSafeVarName(project.environmentName))
|
|
3647
3648
|
}
|
|
3648
3649
|
},
|
|
3649
3650
|
tools: {
|
|
3651
|
+
swc: (swcConfig)=>{
|
|
3652
|
+
swcConfig.env ??= {};
|
|
3653
|
+
swcConfig.env.exclude = Array.from(new Set([
|
|
3654
|
+
...swcConfig.env.exclude ?? [],
|
|
3655
|
+
'transform-parameters'
|
|
3656
|
+
]));
|
|
3657
|
+
},
|
|
3650
3658
|
rspack: (rspackConfig)=>{
|
|
3651
3659
|
rspackConfig.mode = 'development';
|
|
3652
3660
|
applyWebMockRspackConfig(rspackConfig, {
|
|
@@ -3685,21 +3693,46 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3685
3693
|
name: 'rstest:browser-watch',
|
|
3686
3694
|
setup (api) {
|
|
3687
3695
|
api.onBeforeDevCompile(()=>{
|
|
3688
|
-
|
|
3696
|
+
watchState.compileStartTimes.set(project.name, Date.now());
|
|
3697
|
+
if (!watchState.hooksEnabled) return;
|
|
3689
3698
|
logger.log(color.cyan('\nFile changed, re-running tests...\n'));
|
|
3690
3699
|
});
|
|
3691
3700
|
api.onAfterDevCompile(async ({ stats })=>{
|
|
3701
|
+
const compileStart = watchState.compileStartTimes.get(project.name);
|
|
3702
|
+
if (void 0 !== compileStart) {
|
|
3703
|
+
watchState.compileStartTimes.delete(project.name);
|
|
3704
|
+
if (watchState.hooksEnabled) watchState.pendingBuildTimeMs = Math.max(watchState.pendingBuildTimeMs, Date.now() - compileStart);
|
|
3705
|
+
}
|
|
3692
3706
|
if (stats) {
|
|
3693
|
-
const
|
|
3694
|
-
|
|
3707
|
+
const [projectEntry] = await collectProjectEntries(context, [
|
|
3708
|
+
project
|
|
3709
|
+
]);
|
|
3710
|
+
const entryTestFiles = new Set(collectWatchTestFiles(projectEntry ? [
|
|
3711
|
+
projectEntry
|
|
3712
|
+
] : []).map((file)=>file.testPath));
|
|
3713
|
+
const setupFiles = new Set((projectEntry?.setupFiles ?? []).map((file)=>normalize(file)));
|
|
3714
|
+
let state = watchState.invalidation.get(project.name);
|
|
3715
|
+
if (!state) {
|
|
3716
|
+
state = {};
|
|
3717
|
+
watchState.invalidation.set(project.name, state);
|
|
3718
|
+
}
|
|
3695
3719
|
const statsJson = stats.toJson({
|
|
3696
3720
|
all: true
|
|
3697
3721
|
});
|
|
3698
|
-
const affected = getAffectedTestFiles(
|
|
3699
|
-
|
|
3700
|
-
|
|
3722
|
+
const affected = getAffectedTestFiles({
|
|
3723
|
+
chunks: statsJson.chunks,
|
|
3724
|
+
entryTestFiles,
|
|
3725
|
+
setupFiles,
|
|
3726
|
+
state
|
|
3727
|
+
});
|
|
3728
|
+
if (affected.length > 0) {
|
|
3729
|
+
const pending = watchState.pendingAffectedTestFiles.get(project.name) ?? new Set();
|
|
3730
|
+
for (const file of affected)pending.add(file);
|
|
3731
|
+
watchState.pendingAffectedTestFiles.set(project.name, pending);
|
|
3732
|
+
logger.debug(`[Watch] Affected test files: ${affected.join(', ')}`);
|
|
3733
|
+
}
|
|
3701
3734
|
}
|
|
3702
|
-
if (!
|
|
3735
|
+
if (!watchState.hooksEnabled) return;
|
|
3703
3736
|
await onTriggerRerun();
|
|
3704
3737
|
});
|
|
3705
3738
|
}
|
|
@@ -3732,6 +3765,7 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3732
3765
|
});
|
|
3733
3766
|
if (isDebug()) await rsbuildInstance.inspectConfig({
|
|
3734
3767
|
writeToDisk: true,
|
|
3768
|
+
outputPath: external_pathe_resolve(context.rootPath, context.normalizedConfig.output.distPath.root, '.rsbuild'),
|
|
3735
3769
|
extraConfigs: {
|
|
3736
3770
|
rstest: {
|
|
3737
3771
|
...context.normalizedConfig,
|
|
@@ -3844,7 +3878,8 @@ const createBrowserRuntime = async ({ context, projectEntries: initialProjectEnt
|
|
|
3844
3878
|
setContainerOptions,
|
|
3845
3879
|
dispatchHandlers,
|
|
3846
3880
|
wss,
|
|
3847
|
-
projectEntries
|
|
3881
|
+
projectEntries,
|
|
3882
|
+
watchState
|
|
3848
3883
|
};
|
|
3849
3884
|
} catch (error) {
|
|
3850
3885
|
wss.close();
|
|
@@ -4034,12 +4069,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
4034
4069
|
if (reportEmptyTestSet()) return allowEmptyRun ? createEmptyRunResult() : void 0;
|
|
4035
4070
|
}
|
|
4036
4071
|
if (!filesOnly) await notifyTestRunStart();
|
|
4037
|
-
const enableCliShortcuts = isWatchMode &&
|
|
4072
|
+
const enableCliShortcuts = isWatchMode && isTTY('stdin');
|
|
4038
4073
|
const browserTempOutputRoot = context.normalizedConfig.output.distPath.root;
|
|
4039
4074
|
const tempDir = isWatchMode && watchContext.runtime ? watchContext.runtime.tempDir : isWatchMode ? join(context.rootPath, browserTempOutputRoot, 'browser', 'watch') : join(context.rootPath, browserTempOutputRoot, 'browser', Date.now().toString());
|
|
4040
|
-
if (isWatchMode) watchContext.lastTestFiles = collectWatchTestFiles(projectEntries);
|
|
4041
4075
|
let runtime = isWatchMode ? watchContext.runtime : null;
|
|
4042
4076
|
let triggerRerun;
|
|
4077
|
+
let awaitHeadlessRerunIdle;
|
|
4043
4078
|
if (!runtime) {
|
|
4044
4079
|
try {
|
|
4045
4080
|
runtime = await createBrowserRuntime({
|
|
@@ -4068,12 +4103,63 @@ const runBrowserController = async (context, options)=>{
|
|
|
4068
4103
|
}
|
|
4069
4104
|
if (isWatchMode) {
|
|
4070
4105
|
watchContext.runtime = runtime;
|
|
4071
|
-
registerWatchCleanup();
|
|
4072
|
-
if (enableCliShortcuts && !watchContext.closeCliShortcuts) watchContext.closeCliShortcuts = await setupBrowserWatchCliShortcuts({
|
|
4073
|
-
close: cleanupWatchRuntime
|
|
4074
|
-
});
|
|
4106
|
+
registerWatchCleanup(context.embedded);
|
|
4075
4107
|
}
|
|
4076
4108
|
}
|
|
4109
|
+
const watchState = runtime.watchState;
|
|
4110
|
+
if (isWatchMode) watchState.lastTestFiles = collectWatchTestFiles(projectEntries);
|
|
4111
|
+
const seedPendingRerun = (testPaths)=>{
|
|
4112
|
+
const wanted = testPaths ? new Set(testPaths.map((testPath)=>normalize(testPath))) : null;
|
|
4113
|
+
let seeded = 0;
|
|
4114
|
+
for (const file of watchState.lastTestFiles){
|
|
4115
|
+
if (wanted && !wanted.has(file.testPath)) continue;
|
|
4116
|
+
const pending = watchState.pendingAffectedTestFiles.get(file.projectName) ?? new Set();
|
|
4117
|
+
pending.add(file.testPath);
|
|
4118
|
+
watchState.pendingAffectedTestFiles.set(file.projectName, pending);
|
|
4119
|
+
seeded += 1;
|
|
4120
|
+
}
|
|
4121
|
+
return seeded;
|
|
4122
|
+
};
|
|
4123
|
+
const watchHandles = isWatchMode ? {
|
|
4124
|
+
rerun: async (testPaths)=>{
|
|
4125
|
+
const seeded = seedPendingRerun(testPaths);
|
|
4126
|
+
if (testPaths && 0 === seeded) return;
|
|
4127
|
+
await triggerRerun?.();
|
|
4128
|
+
await awaitHeadlessRerunIdle?.();
|
|
4129
|
+
},
|
|
4130
|
+
close: cleanupWatchRuntime
|
|
4131
|
+
} : void 0;
|
|
4132
|
+
const finalizeWatchRerun = async ({ rerunTestPaths, testTime, unhandledErrors })=>{
|
|
4133
|
+
const rerunPathSet = new Set(rerunTestPaths);
|
|
4134
|
+
let sessionCoverage;
|
|
4135
|
+
const coverageMap = buildBrowserCoverageMap(context.reporterResults.results, coverageProvider, {
|
|
4136
|
+
keepResultCoverage: true
|
|
4137
|
+
});
|
|
4138
|
+
if (coverageMap && coverageMap.files().length > 0) sessionCoverage = coverageMap.toJSON();
|
|
4139
|
+
const outcome = {
|
|
4140
|
+
results: context.reporterResults.results.filter((result)=>rerunPathSet.has(result.testPath)),
|
|
4141
|
+
testResults: context.reporterResults.testResults.filter((result)=>rerunPathSet.has(result.testPath)),
|
|
4142
|
+
errors: unhandledErrors ?? [],
|
|
4143
|
+
testPaths: rerunTestPaths,
|
|
4144
|
+
duration: {
|
|
4145
|
+
buildTime: drainPendingBuildTime(watchState),
|
|
4146
|
+
testTime
|
|
4147
|
+
},
|
|
4148
|
+
coverage: sessionCoverage ? {
|
|
4149
|
+
map: sessionCoverage
|
|
4150
|
+
} : void 0,
|
|
4151
|
+
resolveSourcemap: resolveBrowserSourcemap
|
|
4152
|
+
};
|
|
4153
|
+
await finalizeRunCycle(context, {
|
|
4154
|
+
outcomes: [
|
|
4155
|
+
outcome
|
|
4156
|
+
],
|
|
4157
|
+
mode: 'on-demand',
|
|
4158
|
+
isWatchMode: true,
|
|
4159
|
+
coverageProvider,
|
|
4160
|
+
reportOnFailure: coverageConfig?.reportOnFailure ?? false
|
|
4161
|
+
});
|
|
4162
|
+
};
|
|
4077
4163
|
projectEntries = runtime.projectEntries;
|
|
4078
4164
|
totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
4079
4165
|
const buildTime = Date.now() - buildStart;
|
|
@@ -4120,7 +4206,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4120
4206
|
rootPath: normalize(context.rootPath),
|
|
4121
4207
|
projects: projectRuntimeConfigs,
|
|
4122
4208
|
snapshot: {
|
|
4123
|
-
updateSnapshot: context.snapshotManager.options.updateSnapshot
|
|
4209
|
+
updateSnapshot: options?.updateSnapshot ?? context.snapshotManager.options.updateSnapshot
|
|
4124
4210
|
},
|
|
4125
4211
|
runnerUrl: `http://localhost:${runtime.containerServer.port}`,
|
|
4126
4212
|
projectRunnerUrls,
|
|
@@ -4461,6 +4547,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
4461
4547
|
});
|
|
4462
4548
|
const inlineOptions = {
|
|
4463
4549
|
...hostOptions,
|
|
4550
|
+
snapshot: {
|
|
4551
|
+
updateSnapshot: context.snapshotManager.options.updateSnapshot
|
|
4552
|
+
},
|
|
4464
4553
|
testFile: file.testPath,
|
|
4465
4554
|
runId: `${run.token}:${session.id}`
|
|
4466
4555
|
};
|
|
@@ -4573,20 +4662,16 @@ const runBrowserController = async (context, options)=>{
|
|
|
4573
4662
|
} finally{
|
|
4574
4663
|
const testTime = Math.max(0, Date.now() - rerunStartTime);
|
|
4575
4664
|
const rerunFatalError = fatalError && fatalError !== fatalErrorBeforeRun ? fatalError : void 0;
|
|
4576
|
-
await
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
buildTime: 0,
|
|
4580
|
-
testTime
|
|
4581
|
-
},
|
|
4582
|
-
filterRerunTestPaths: files.map((file)=>file.testPath),
|
|
4665
|
+
await finalizeWatchRerun({
|
|
4666
|
+
rerunTestPaths: files.map((file)=>file.testPath),
|
|
4667
|
+
testTime,
|
|
4583
4668
|
unhandledErrors: rerunError ? [
|
|
4584
4669
|
rerunError
|
|
4585
4670
|
] : rerunFatalError ? [
|
|
4586
4671
|
rerunFatalError
|
|
4587
4672
|
] : void 0
|
|
4588
4673
|
});
|
|
4589
|
-
|
|
4674
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4590
4675
|
}
|
|
4591
4676
|
},
|
|
4592
4677
|
onError: async (error)=>{
|
|
@@ -4600,6 +4685,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4600
4685
|
logger.debug(`[Headless] Interrupting active run token ${run.token} before scheduling latest rerun`);
|
|
4601
4686
|
}
|
|
4602
4687
|
});
|
|
4688
|
+
awaitHeadlessRerunIdle = ()=>latestRerunScheduler.whenIdle();
|
|
4603
4689
|
if (0 === allTestFiles.length) {
|
|
4604
4690
|
const duration = {
|
|
4605
4691
|
totalTime: buildTime,
|
|
@@ -4616,7 +4702,8 @@ const runBrowserController = async (context, options)=>{
|
|
|
4616
4702
|
close: isWatchMode ? void 0 : async ()=>{
|
|
4617
4703
|
sessionRegistry.clear();
|
|
4618
4704
|
await destroyBrowserRuntime(runtime);
|
|
4619
|
-
}
|
|
4705
|
+
},
|
|
4706
|
+
watch: watchHandles
|
|
4620
4707
|
};
|
|
4621
4708
|
if (isWatchMode) await notifyTestRunEnd({
|
|
4622
4709
|
duration
|
|
@@ -4626,25 +4713,24 @@ const runBrowserController = async (context, options)=>{
|
|
|
4626
4713
|
const newProjectEntries = await collectProjectEntries(context);
|
|
4627
4714
|
const rerunPlan = planWatchRerun({
|
|
4628
4715
|
projectEntries: newProjectEntries,
|
|
4629
|
-
previousTestFiles:
|
|
4630
|
-
affectedTestFiles:
|
|
4716
|
+
previousTestFiles: watchState.lastTestFiles,
|
|
4717
|
+
affectedTestFiles: drainPendingAffectedTestFiles(watchState)
|
|
4631
4718
|
});
|
|
4632
|
-
watchContext.affectedTestFiles = [];
|
|
4633
4719
|
if (rerunPlan.filesChanged) {
|
|
4634
|
-
|
|
4720
|
+
watchState.lastTestFiles = rerunPlan.currentTestFiles;
|
|
4635
4721
|
if (0 === rerunPlan.currentTestFiles.length) {
|
|
4636
4722
|
logger.log(color.cyan('No browser test files remain after update.\n'));
|
|
4637
|
-
|
|
4723
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4638
4724
|
return;
|
|
4639
4725
|
}
|
|
4640
4726
|
logger.log(color.cyan(`Test file set changed, re-running ${rerunPlan.currentTestFiles.length} file(s)...\n`));
|
|
4641
|
-
latestRerunScheduler.enqueueLatest(rerunPlan.currentTestFiles);
|
|
4727
|
+
await latestRerunScheduler.enqueueLatest(rerunPlan.currentTestFiles);
|
|
4642
4728
|
return;
|
|
4643
4729
|
}
|
|
4644
|
-
|
|
4730
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4645
4731
|
};
|
|
4646
|
-
|
|
4647
|
-
|
|
4732
|
+
watchState.hooksEnabled = true;
|
|
4733
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4648
4734
|
}
|
|
4649
4735
|
return result;
|
|
4650
4736
|
}
|
|
@@ -4655,31 +4741,30 @@ const runBrowserController = async (context, options)=>{
|
|
|
4655
4741
|
const newProjectEntries = await collectProjectEntries(context);
|
|
4656
4742
|
const rerunPlan = planWatchRerun({
|
|
4657
4743
|
projectEntries: newProjectEntries,
|
|
4658
|
-
previousTestFiles:
|
|
4659
|
-
affectedTestFiles:
|
|
4744
|
+
previousTestFiles: watchState.lastTestFiles,
|
|
4745
|
+
affectedTestFiles: drainPendingAffectedTestFiles(watchState)
|
|
4660
4746
|
});
|
|
4661
|
-
watchContext.affectedTestFiles = [];
|
|
4662
4747
|
if (rerunPlan.filesChanged) {
|
|
4663
|
-
const deletedTestPaths = collectDeletedTestPaths(
|
|
4748
|
+
const deletedTestPaths = collectDeletedTestPaths(watchState.lastTestFiles, rerunPlan.currentTestFiles);
|
|
4664
4749
|
if (deletedTestPaths.length > 0) context.updateReporterResultState([], [], deletedTestPaths);
|
|
4665
|
-
|
|
4750
|
+
watchState.lastTestFiles = rerunPlan.currentTestFiles;
|
|
4666
4751
|
if (0 === rerunPlan.currentTestFiles.length) {
|
|
4667
4752
|
await latestRerunScheduler.enqueueLatest([]);
|
|
4668
4753
|
logger.log(color.cyan('No browser test files remain after update.\n'));
|
|
4669
|
-
|
|
4754
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4670
4755
|
return;
|
|
4671
4756
|
}
|
|
4672
4757
|
logger.log(color.cyan(`Test file set changed, re-running ${rerunPlan.currentTestFiles.length} file(s)...\n`));
|
|
4673
|
-
latestRerunScheduler.enqueueLatest(rerunPlan.currentTestFiles);
|
|
4758
|
+
await latestRerunScheduler.enqueueLatest(rerunPlan.currentTestFiles);
|
|
4674
4759
|
return;
|
|
4675
4760
|
}
|
|
4676
4761
|
if (0 === rerunPlan.affectedTestFiles.length) {
|
|
4677
4762
|
logger.log(color.cyan('No affected browser test files detected, skipping re-run.\n'));
|
|
4678
|
-
|
|
4763
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4679
4764
|
return;
|
|
4680
4765
|
}
|
|
4681
4766
|
logger.log(color.cyan(`Re-running ${rerunPlan.affectedTestFiles.length} affected test file(s)...\n`));
|
|
4682
|
-
latestRerunScheduler.enqueueLatest(rerunPlan.affectedTestFiles);
|
|
4767
|
+
await latestRerunScheduler.enqueueLatest(rerunPlan.affectedTestFiles);
|
|
4683
4768
|
};
|
|
4684
4769
|
const closeHeadlessRuntime = isWatchMode ? void 0 : async ()=>{
|
|
4685
4770
|
sessionRegistry.clear();
|
|
@@ -4701,7 +4786,8 @@ const runBrowserController = async (context, options)=>{
|
|
|
4701
4786
|
hasFailure: isFailure,
|
|
4702
4787
|
getSourcemap: getBrowserSourcemap,
|
|
4703
4788
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4704
|
-
close: closeHeadlessRuntime
|
|
4789
|
+
close: closeHeadlessRuntime,
|
|
4790
|
+
watch: watchHandles
|
|
4705
4791
|
};
|
|
4706
4792
|
if (isWatchMode) try {
|
|
4707
4793
|
await notifyTestRunEnd({
|
|
@@ -4711,8 +4797,8 @@ const runBrowserController = async (context, options)=>{
|
|
|
4711
4797
|
await closeHeadlessRuntime?.();
|
|
4712
4798
|
}
|
|
4713
4799
|
if (isWatchMode && triggerRerun) {
|
|
4714
|
-
|
|
4715
|
-
|
|
4800
|
+
watchState.hooksEnabled = true;
|
|
4801
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4716
4802
|
}
|
|
4717
4803
|
return result;
|
|
4718
4804
|
}
|
|
@@ -4919,22 +5005,31 @@ const runBrowserController = async (context, options)=>{
|
|
|
4919
5005
|
testTime = Date.now() - testStart;
|
|
4920
5006
|
}
|
|
4921
5007
|
if (isWatchMode) triggerRerun = async ()=>{
|
|
4922
|
-
const
|
|
5008
|
+
const refreshedHostOptions = {
|
|
5009
|
+
...hostOptions,
|
|
5010
|
+
snapshot: {
|
|
5011
|
+
updateSnapshot: context.snapshotManager.options.updateSnapshot
|
|
5012
|
+
}
|
|
5013
|
+
};
|
|
5014
|
+
runtime.setContainerOptions(refreshedHostOptions);
|
|
5015
|
+
const [, newProjectEntries] = await Promise.all([
|
|
5016
|
+
rpcManager.updateHostConfig(refreshedHostOptions),
|
|
5017
|
+
collectProjectEntries(context)
|
|
5018
|
+
]);
|
|
4923
5019
|
const rerunPlan = planWatchRerun({
|
|
4924
5020
|
projectEntries: newProjectEntries,
|
|
4925
|
-
previousTestFiles:
|
|
4926
|
-
affectedTestFiles:
|
|
5021
|
+
previousTestFiles: watchState.lastTestFiles,
|
|
5022
|
+
affectedTestFiles: drainPendingAffectedTestFiles(watchState)
|
|
4927
5023
|
});
|
|
4928
|
-
watchContext.affectedTestFiles = [];
|
|
4929
5024
|
if (rerunPlan.filesChanged) {
|
|
4930
|
-
const deletedTestPaths = collectDeletedTestPaths(
|
|
5025
|
+
const deletedTestPaths = collectDeletedTestPaths(watchState.lastTestFiles, rerunPlan.currentTestFiles);
|
|
4931
5026
|
if (deletedTestPaths.length > 0) context.updateReporterResultState([], [], deletedTestPaths);
|
|
4932
|
-
|
|
5027
|
+
watchState.lastTestFiles = rerunPlan.currentTestFiles;
|
|
4933
5028
|
currentTestFiles = rerunPlan.currentTestFiles;
|
|
4934
5029
|
await rpcManager.notifyTestFileUpdate(currentTestFiles);
|
|
4935
5030
|
if (0 === currentTestFiles.length) {
|
|
4936
5031
|
logger.log(color.cyan('No browser test files remain after update.\n'));
|
|
4937
|
-
|
|
5032
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4938
5033
|
return;
|
|
4939
5034
|
}
|
|
4940
5035
|
await waitForRunnerFramesReady(currentTestFiles.map((file)=>file.testPath));
|
|
@@ -4954,25 +5049,21 @@ const runBrowserController = async (context, options)=>{
|
|
|
4954
5049
|
} finally{
|
|
4955
5050
|
const testTime = Math.max(0, Date.now() - rerunStartTime);
|
|
4956
5051
|
const rerunFatalError = fatalError && fatalError !== fatalErrorBeforeRun ? fatalError : void 0;
|
|
4957
|
-
await
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
buildTime: 0,
|
|
4961
|
-
testTime
|
|
4962
|
-
},
|
|
4963
|
-
filterRerunTestPaths: rerunPlan.normalizedAffectedTestFiles,
|
|
5052
|
+
await finalizeWatchRerun({
|
|
5053
|
+
rerunTestPaths: rerunPlan.normalizedAffectedTestFiles,
|
|
5054
|
+
testTime,
|
|
4964
5055
|
unhandledErrors: rerunError ? [
|
|
4965
5056
|
rerunError
|
|
4966
5057
|
] : rerunFatalError ? [
|
|
4967
5058
|
rerunFatalError
|
|
4968
5059
|
] : void 0
|
|
4969
5060
|
});
|
|
4970
|
-
|
|
5061
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4971
5062
|
}
|
|
4972
|
-
} else if (rerunPlan.filesChanged)
|
|
5063
|
+
} else if (rerunPlan.filesChanged) logWatchReadyMessage(context, enableCliShortcuts);
|
|
4973
5064
|
else {
|
|
4974
5065
|
logger.log(color.cyan('Tests will be re-executed automatically\n'));
|
|
4975
|
-
|
|
5066
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
4976
5067
|
}
|
|
4977
5068
|
};
|
|
4978
5069
|
const closeContainerRuntime = isWatchMode ? void 0 : async ()=>{
|
|
@@ -5000,7 +5091,8 @@ const runBrowserController = async (context, options)=>{
|
|
|
5000
5091
|
hasFailure: isFailure,
|
|
5001
5092
|
getSourcemap: getBrowserSourcemap,
|
|
5002
5093
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
5003
|
-
close: closeContainerRuntime
|
|
5094
|
+
close: closeContainerRuntime,
|
|
5095
|
+
watch: watchHandles
|
|
5004
5096
|
};
|
|
5005
5097
|
if (isWatchMode) try {
|
|
5006
5098
|
await notifyTestRunEnd({
|
|
@@ -5010,8 +5102,8 @@ const runBrowserController = async (context, options)=>{
|
|
|
5010
5102
|
await closeContainerRuntime?.();
|
|
5011
5103
|
}
|
|
5012
5104
|
if (isWatchMode && triggerRerun) {
|
|
5013
|
-
|
|
5014
|
-
|
|
5105
|
+
watchState.hooksEnabled = true;
|
|
5106
|
+
logWatchReadyMessage(context, enableCliShortcuts);
|
|
5015
5107
|
}
|
|
5016
5108
|
return result;
|
|
5017
5109
|
};
|
|
@@ -5204,7 +5296,7 @@ const emptyOutcome = ()=>({
|
|
|
5204
5296
|
}
|
|
5205
5297
|
});
|
|
5206
5298
|
async function createBrowserExecutor(context, options) {
|
|
5207
|
-
const { projects, coverageProvider, freezeShardedEntries, filesOnly, allowEmptyRun, appliedModifyRstestConfigEnvironments } = options;
|
|
5299
|
+
const { projects, coverageProvider, shardedEntries, freezeShardedEntries, filesOnly, allowEmptyRun, appliedModifyRstestConfigEnvironments } = options;
|
|
5208
5300
|
let deferredClose;
|
|
5209
5301
|
let inFlightCycle;
|
|
5210
5302
|
const foldOutcome = (result)=>{
|
|
@@ -5232,12 +5324,13 @@ async function createBrowserExecutor(context, options) {
|
|
|
5232
5324
|
async runCycle (opts) {
|
|
5233
5325
|
const cycle = runBrowserController(context, {
|
|
5234
5326
|
projects,
|
|
5235
|
-
shardedEntries
|
|
5327
|
+
shardedEntries,
|
|
5236
5328
|
freezeShardedEntries,
|
|
5237
5329
|
allowEmptyRun,
|
|
5238
5330
|
appliedModifyRstestConfigEnvironments,
|
|
5239
5331
|
onTraceEvents: opts.onTraceEvents,
|
|
5240
|
-
env: opts.env
|
|
5332
|
+
env: opts.env,
|
|
5333
|
+
updateSnapshot: opts.updateSnapshot
|
|
5241
5334
|
});
|
|
5242
5335
|
inFlightCycle = cycle;
|
|
5243
5336
|
try {
|
|
@@ -5248,10 +5341,10 @@ async function createBrowserExecutor(context, options) {
|
|
|
5248
5341
|
inFlightCycle = void 0;
|
|
5249
5342
|
}
|
|
5250
5343
|
},
|
|
5251
|
-
async collect (
|
|
5344
|
+
async collect () {
|
|
5252
5345
|
const pending = listBrowserTests(context, {
|
|
5253
5346
|
projects,
|
|
5254
|
-
shardedEntries
|
|
5347
|
+
shardedEntries,
|
|
5255
5348
|
freezeShardedEntries,
|
|
5256
5349
|
filesOnly,
|
|
5257
5350
|
appliedModifyRstestConfigEnvironments
|