@rstest/browser 0.9.0 → 0.9.2
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/{101.36a8ccdf84.js → 101.82cdbbe145.js} +852 -824
- package/dist/browser-container/container-static/js/101.82cdbbe145.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.0687a8142a.js → index.602d6770fe.js} +91 -84
- package/dist/browser-container/container-static/js/{lib-react.dcf2a5e57a.js → lib-react.ce60b6aea5.js} +3 -3
- package/dist/browser-container/container-static/js/lib-react.ce60b6aea5.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.js +1 -1
- package/dist/headedSerialTaskQueue.d.ts +8 -0
- package/dist/hostController.d.ts +16 -0
- package/dist/index.js +219 -47
- package/dist/watchCliShortcuts.d.ts +6 -0
- package/package.json +7 -7
- package/src/headedSerialTaskQueue.ts +19 -0
- package/src/hostController.ts +294 -65
- package/src/watchCliShortcuts.ts +77 -0
- package/dist/browser-container/container-static/js/101.36a8ccdf84.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.dcf2a5e57a.js.LICENSE.txt +0 -1
- /package/dist/{361.js → 323.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import sirv from "sirv";
|
|
|
9
9
|
import { WebSocketServer } from "ws";
|
|
10
10
|
import node_os from "node:os";
|
|
11
11
|
import convert_source_map from "convert-source-map";
|
|
12
|
-
import { DISPATCH_RPC_BRIDGE_NAME, DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_RUNNER } from "./
|
|
12
|
+
import { DISPATCH_RPC_BRIDGE_NAME, DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_RUNNER } from "./323.js";
|
|
13
13
|
__webpack_require__.add({
|
|
14
14
|
"../../node_modules/.pnpm/picomatch@4.0.3/node_modules/picomatch/index.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
15
15
|
const pico = __webpack_require__("../../node_modules/.pnpm/picomatch@4.0.3/node_modules/picomatch/lib/picomatch.js");
|
|
@@ -1760,6 +1760,17 @@ const createHostDispatchRouter = ({ routerOptions, runnerCallbacks, runSnapshotR
|
|
|
1760
1760
|
}
|
|
1761
1761
|
return router;
|
|
1762
1762
|
};
|
|
1763
|
+
const createHeadedSerialTaskQueue = ()=>{
|
|
1764
|
+
let queue = Promise.resolve();
|
|
1765
|
+
const enqueue = (task)=>{
|
|
1766
|
+
const next = queue.then(task);
|
|
1767
|
+
queue = next.catch(()=>{});
|
|
1768
|
+
return next;
|
|
1769
|
+
};
|
|
1770
|
+
return {
|
|
1771
|
+
enqueue
|
|
1772
|
+
};
|
|
1773
|
+
};
|
|
1763
1774
|
const createHeadlessLatestRerunScheduler = (options)=>{
|
|
1764
1775
|
let pendingFiles = null;
|
|
1765
1776
|
let draining = null;
|
|
@@ -2490,6 +2501,45 @@ const resolveBrowserViewportPreset = (presetId)=>{
|
|
|
2490
2501
|
const size = BROWSER_VIEWPORT_PRESET_DIMENSIONS[presetId];
|
|
2491
2502
|
return size ?? null;
|
|
2492
2503
|
};
|
|
2504
|
+
const isTTY = ()=>Boolean(process.stdin.isTTY && !process.env.CI);
|
|
2505
|
+
const isBrowserWatchCliShortcutsEnabled = ()=>isTTY();
|
|
2506
|
+
const getBrowserWatchCliShortcutsHintMessage = ()=>` ${color.dim('press')} ${color.bold('q')} ${color.dim('to quit')}\n`;
|
|
2507
|
+
const logBrowserWatchReadyMessage = (enableCliShortcuts)=>{
|
|
2508
|
+
logger.log(color.green(' Waiting for file changes...'));
|
|
2509
|
+
if (enableCliShortcuts) logger.log(getBrowserWatchCliShortcutsHintMessage());
|
|
2510
|
+
};
|
|
2511
|
+
async function setupBrowserWatchCliShortcuts({ close }) {
|
|
2512
|
+
const { emitKeypressEvents } = await import("node:readline");
|
|
2513
|
+
emitKeypressEvents(process.stdin);
|
|
2514
|
+
process.stdin.setRawMode(true);
|
|
2515
|
+
process.stdin.resume();
|
|
2516
|
+
process.stdin.setEncoding('utf8');
|
|
2517
|
+
let isClosing = false;
|
|
2518
|
+
const handleKeypress = (str, key)=>{
|
|
2519
|
+
if (key.ctrl && 'c' === key.name) return void process.kill(process.pid, 'SIGINT');
|
|
2520
|
+
if (key.ctrl && 'z' === key.name) {
|
|
2521
|
+
if ('win32' !== process.platform) process.kill(process.pid, 'SIGTSTP');
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
if ('q' !== str || isClosing) return;
|
|
2525
|
+
isClosing = true;
|
|
2526
|
+
(async ()=>{
|
|
2527
|
+
try {
|
|
2528
|
+
await close();
|
|
2529
|
+
} finally{
|
|
2530
|
+
process.exit(0);
|
|
2531
|
+
}
|
|
2532
|
+
})();
|
|
2533
|
+
};
|
|
2534
|
+
process.stdin.on('keypress', handleKeypress);
|
|
2535
|
+
return ()=>{
|
|
2536
|
+
try {
|
|
2537
|
+
process.stdin.setRawMode(false);
|
|
2538
|
+
process.stdin.pause();
|
|
2539
|
+
} catch {}
|
|
2540
|
+
process.stdin.off('keypress', handleKeypress);
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2493
2543
|
const serializeTestFiles = (files)=>JSON.stringify(files.map((f)=>`${f.projectName}:${f.testPath}`).sort());
|
|
2494
2544
|
const normalizeTestFiles = (files)=>files.map((file)=>({
|
|
2495
2545
|
...file,
|
|
@@ -2588,6 +2638,8 @@ const watchContext = {
|
|
|
2588
2638
|
lastTestFiles: [],
|
|
2589
2639
|
hooksEnabled: false,
|
|
2590
2640
|
cleanupRegistered: false,
|
|
2641
|
+
cleanupPromise: null,
|
|
2642
|
+
closeCliShortcuts: null,
|
|
2591
2643
|
chunkHashes: new Map(),
|
|
2592
2644
|
affectedTestFiles: []
|
|
2593
2645
|
};
|
|
@@ -2611,6 +2663,43 @@ const mapViewportByProject = (projects)=>{
|
|
|
2611
2663
|
const ensureProcessExitCode = (code)=>{
|
|
2612
2664
|
if (void 0 === process.exitCode || 0 === process.exitCode) process.exitCode = code;
|
|
2613
2665
|
};
|
|
2666
|
+
const castArray = (arr)=>{
|
|
2667
|
+
if (void 0 === arr) return [];
|
|
2668
|
+
return Array.isArray(arr) ? arr : [
|
|
2669
|
+
arr
|
|
2670
|
+
];
|
|
2671
|
+
};
|
|
2672
|
+
const applyDefaultWatchOptions = (rspackConfig, isWatchMode)=>{
|
|
2673
|
+
rspackConfig.watchOptions ??= {};
|
|
2674
|
+
if (!isWatchMode) {
|
|
2675
|
+
rspackConfig.watchOptions.ignored = '**/**';
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
rspackConfig.watchOptions.ignored = castArray(rspackConfig.watchOptions.ignored || []);
|
|
2679
|
+
if (0 === rspackConfig.watchOptions.ignored.length) rspackConfig.watchOptions.ignored.push('**/.git', '**/node_modules');
|
|
2680
|
+
rspackConfig.output?.path && rspackConfig.watchOptions.ignored.push(rspackConfig.output.path);
|
|
2681
|
+
};
|
|
2682
|
+
const createBrowserLazyCompilationConfig = (setupFiles)=>{
|
|
2683
|
+
const eagerSetupFiles = new Set(setupFiles.map((filePath)=>normalize(filePath)));
|
|
2684
|
+
if (0 === eagerSetupFiles.size) return {
|
|
2685
|
+
imports: true,
|
|
2686
|
+
entries: false
|
|
2687
|
+
};
|
|
2688
|
+
return {
|
|
2689
|
+
imports: true,
|
|
2690
|
+
entries: false,
|
|
2691
|
+
test (module) {
|
|
2692
|
+
const filePath = module.nameForCondition?.();
|
|
2693
|
+
return !filePath || !eagerSetupFiles.has(normalize(filePath));
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2696
|
+
};
|
|
2697
|
+
const createBrowserRsbuildDevConfig = (isWatchMode)=>({
|
|
2698
|
+
hmr: isWatchMode,
|
|
2699
|
+
client: {
|
|
2700
|
+
logLevel: 'error'
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2614
2703
|
const globToRegexp = (glob)=>{
|
|
2615
2704
|
const regex = picomatch.makeRe(glob, {
|
|
2616
2705
|
fastpaths: false,
|
|
@@ -2873,21 +2962,28 @@ const destroyBrowserRuntime = async (runtime)=>{
|
|
|
2873
2962
|
force: true
|
|
2874
2963
|
}).catch(()=>{});
|
|
2875
2964
|
};
|
|
2876
|
-
const
|
|
2877
|
-
if (watchContext.
|
|
2878
|
-
|
|
2965
|
+
const cleanupWatchRuntime = ()=>{
|
|
2966
|
+
if (watchContext.cleanupPromise) return watchContext.cleanupPromise;
|
|
2967
|
+
watchContext.cleanupPromise = (async ()=>{
|
|
2968
|
+
watchContext.closeCliShortcuts?.();
|
|
2969
|
+
watchContext.closeCliShortcuts = null;
|
|
2879
2970
|
if (!watchContext.runtime) return;
|
|
2880
2971
|
await destroyBrowserRuntime(watchContext.runtime);
|
|
2881
2972
|
watchContext.runtime = null;
|
|
2882
|
-
};
|
|
2973
|
+
})();
|
|
2974
|
+
return watchContext.cleanupPromise;
|
|
2975
|
+
};
|
|
2976
|
+
const registerWatchCleanup = ()=>{
|
|
2977
|
+
if (watchContext.cleanupRegistered) return;
|
|
2883
2978
|
for (const signal of [
|
|
2884
2979
|
'SIGINT',
|
|
2885
|
-
'SIGTERM'
|
|
2980
|
+
'SIGTERM',
|
|
2981
|
+
'SIGTSTP'
|
|
2886
2982
|
])process.once(signal, ()=>{
|
|
2887
|
-
|
|
2983
|
+
cleanupWatchRuntime();
|
|
2888
2984
|
});
|
|
2889
2985
|
process.once('exit', ()=>{
|
|
2890
|
-
|
|
2986
|
+
cleanupWatchRuntime();
|
|
2891
2987
|
});
|
|
2892
2988
|
watchContext.cleanupRegistered = true;
|
|
2893
2989
|
};
|
|
@@ -2929,11 +3025,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2929
3025
|
port: browserLaunchOptions.port ?? 4000,
|
|
2930
3026
|
strictPort: browserLaunchOptions.strictPort
|
|
2931
3027
|
},
|
|
2932
|
-
dev:
|
|
2933
|
-
client: {
|
|
2934
|
-
logLevel: 'error'
|
|
2935
|
-
}
|
|
2936
|
-
},
|
|
3028
|
+
dev: createBrowserRsbuildDevConfig(isWatchMode),
|
|
2937
3029
|
environments: {
|
|
2938
3030
|
...Object.fromEntries(browserProjects.map((project)=>[
|
|
2939
3031
|
project.environmentName,
|
|
@@ -2956,6 +3048,7 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2956
3048
|
const project = projectByEnvironmentName.get(name);
|
|
2957
3049
|
if (!project) return config;
|
|
2958
3050
|
const userRsbuildConfig = project.normalizedConfig;
|
|
3051
|
+
const setupFiles = Object.values(getSetupFiles(project.normalizedConfig.setupFiles, project.rootPath));
|
|
2959
3052
|
const merged = mergeEnvironmentConfig(config, userRsbuildConfig, {
|
|
2960
3053
|
resolve: {
|
|
2961
3054
|
alias: rstestInternalAliases
|
|
@@ -2975,12 +3068,10 @@ const createBrowserRuntime = async ({ context, manifestPath, manifestSource, tem
|
|
|
2975
3068
|
tools: {
|
|
2976
3069
|
rspack: (rspackConfig)=>{
|
|
2977
3070
|
rspackConfig.mode = 'development';
|
|
2978
|
-
rspackConfig.lazyCompilation =
|
|
2979
|
-
imports: true,
|
|
2980
|
-
entries: false
|
|
2981
|
-
};
|
|
3071
|
+
rspackConfig.lazyCompilation = createBrowserLazyCompilationConfig(setupFiles);
|
|
2982
3072
|
rspackConfig.plugins = rspackConfig.plugins || [];
|
|
2983
3073
|
rspackConfig.plugins.push(virtualManifestPlugin);
|
|
3074
|
+
applyDefaultWatchOptions(rspackConfig, isWatchMode);
|
|
2984
3075
|
const browserRuntimeDir = dirname(browserRuntimePath);
|
|
2985
3076
|
rspackConfig.module = rspackConfig.module || {};
|
|
2986
3077
|
rspackConfig.module.rules = rspackConfig.module.rules || [];
|
|
@@ -3314,6 +3405,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3314
3405
|
}
|
|
3315
3406
|
await notifyTestRunStart();
|
|
3316
3407
|
const isWatchMode = 'watch' === context.command;
|
|
3408
|
+
const enableCliShortcuts = isWatchMode && isBrowserWatchCliShortcutsEnabled();
|
|
3317
3409
|
const tempDir = isWatchMode && watchContext.runtime ? watchContext.runtime.tempDir : isWatchMode ? join(context.rootPath, TEMP_RSTEST_OUTPUT_DIR, 'browser', 'watch') : join(context.rootPath, TEMP_RSTEST_OUTPUT_DIR, 'browser', Date.now().toString());
|
|
3318
3410
|
const manifestPath = join(tempDir, VIRTUAL_MANIFEST_FILENAME);
|
|
3319
3411
|
const manifestSource = generateManifestModule({
|
|
@@ -3348,6 +3440,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
3348
3440
|
if (isWatchMode) {
|
|
3349
3441
|
watchContext.runtime = runtime;
|
|
3350
3442
|
registerWatchCleanup();
|
|
3443
|
+
if (enableCliShortcuts && !watchContext.closeCliShortcuts) watchContext.closeCliShortcuts = await setupBrowserWatchCliShortcuts({
|
|
3444
|
+
close: cleanupWatchRuntime
|
|
3445
|
+
});
|
|
3351
3446
|
}
|
|
3352
3447
|
}
|
|
3353
3448
|
const { browser, port, wsPort, wss } = runtime;
|
|
@@ -3734,6 +3829,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3734
3829
|
rerunFatalError
|
|
3735
3830
|
] : void 0
|
|
3736
3831
|
});
|
|
3832
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
3737
3833
|
}
|
|
3738
3834
|
},
|
|
3739
3835
|
onError: async (error)=>{
|
|
@@ -3765,13 +3861,18 @@ const runBrowserController = async (context, options)=>{
|
|
|
3765
3861
|
if (0 === rerunPlan.currentTestFiles.length) {
|
|
3766
3862
|
await latestRerunScheduler.enqueueLatest([]);
|
|
3767
3863
|
logger.log(color.cyan('No browser test files remain after update.\n'));
|
|
3864
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
3768
3865
|
return;
|
|
3769
3866
|
}
|
|
3770
3867
|
logger.log(color.cyan(`Test file set changed, re-running ${rerunPlan.currentTestFiles.length} file(s)...\n`));
|
|
3771
3868
|
latestRerunScheduler.enqueueLatest(rerunPlan.currentTestFiles);
|
|
3772
3869
|
return;
|
|
3773
3870
|
}
|
|
3774
|
-
if (0 === rerunPlan.affectedTestFiles.length)
|
|
3871
|
+
if (0 === rerunPlan.affectedTestFiles.length) {
|
|
3872
|
+
logger.log(color.cyan('No affected browser test files detected, skipping re-run.\n'));
|
|
3873
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3775
3876
|
logger.log(color.cyan(`Re-running ${rerunPlan.affectedTestFiles.length} affected test file(s)...\n`));
|
|
3776
3877
|
latestRerunScheduler.enqueueLatest(rerunPlan.affectedTestFiles);
|
|
3777
3878
|
};
|
|
@@ -3806,15 +3907,59 @@ const runBrowserController = async (context, options)=>{
|
|
|
3806
3907
|
}
|
|
3807
3908
|
if (isWatchMode && triggerRerun) {
|
|
3808
3909
|
watchContext.hooksEnabled = true;
|
|
3809
|
-
|
|
3910
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
3810
3911
|
}
|
|
3811
3912
|
return result;
|
|
3812
3913
|
}
|
|
3813
|
-
let
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3914
|
+
let currentTestFiles = allTestFiles;
|
|
3915
|
+
const RUNNER_FRAMES_READY_TIMEOUT_MS = 30000;
|
|
3916
|
+
let currentRunnerFramesSignature = null;
|
|
3917
|
+
const runnerFramesWaiters = new Map();
|
|
3918
|
+
const createTestFilesSignature = (testFiles)=>JSON.stringify(testFiles.map((testFile)=>normalize(testFile)));
|
|
3919
|
+
const markRunnerFramesReady = (testFiles)=>{
|
|
3920
|
+
const signature = createTestFilesSignature(testFiles);
|
|
3921
|
+
currentRunnerFramesSignature = signature;
|
|
3922
|
+
const waiters = runnerFramesWaiters.get(signature);
|
|
3923
|
+
if (!waiters) return;
|
|
3924
|
+
runnerFramesWaiters.delete(signature);
|
|
3925
|
+
for (const waiter of waiters)waiter();
|
|
3926
|
+
};
|
|
3927
|
+
const waitForRunnerFramesReady = async (testFiles)=>{
|
|
3928
|
+
const signature = createTestFilesSignature(testFiles);
|
|
3929
|
+
if (currentRunnerFramesSignature === signature) return;
|
|
3930
|
+
await new Promise((resolve, reject)=>{
|
|
3931
|
+
const waiters = runnerFramesWaiters.get(signature) ?? new Set();
|
|
3932
|
+
let timeoutId;
|
|
3933
|
+
const cleanup = ()=>{
|
|
3934
|
+
const currentWaiters = runnerFramesWaiters.get(signature);
|
|
3935
|
+
if (!currentWaiters) return;
|
|
3936
|
+
currentWaiters.delete(onReady);
|
|
3937
|
+
if (0 === currentWaiters.size) runnerFramesWaiters.delete(signature);
|
|
3938
|
+
};
|
|
3939
|
+
const onReady = ()=>{
|
|
3940
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
3941
|
+
cleanup();
|
|
3942
|
+
resolve();
|
|
3943
|
+
};
|
|
3944
|
+
timeoutId = setTimeout(()=>{
|
|
3945
|
+
cleanup();
|
|
3946
|
+
reject(new Error(`Timed out waiting for headed runner frames to be ready for ${testFiles.length} file(s).`));
|
|
3947
|
+
}, RUNNER_FRAMES_READY_TIMEOUT_MS);
|
|
3948
|
+
waiters.add(onReady);
|
|
3949
|
+
runnerFramesWaiters.set(signature, waiters);
|
|
3950
|
+
if (currentRunnerFramesSignature === signature) onReady();
|
|
3951
|
+
});
|
|
3952
|
+
};
|
|
3953
|
+
const getTestFileInfo = (testFile)=>{
|
|
3954
|
+
const normalizedTestFile = normalize(testFile);
|
|
3955
|
+
const fileInfo = currentTestFiles.find((file)=>file.testPath === normalizedTestFile);
|
|
3956
|
+
if (!fileInfo) throw new Error(`Unknown browser test file: ${JSON.stringify(testFile)}`);
|
|
3957
|
+
return fileInfo;
|
|
3958
|
+
};
|
|
3959
|
+
const getHeadedPerFileTimeoutMs = (file)=>{
|
|
3960
|
+
const projectRuntime = projectRuntimeConfigs.find((project)=>project.name === file.projectName);
|
|
3961
|
+
return (projectRuntime?.runtimeConfig.testTimeout ?? maxTestTimeoutForRpc) + 30000;
|
|
3962
|
+
};
|
|
3818
3963
|
let containerContext;
|
|
3819
3964
|
let containerPage;
|
|
3820
3965
|
let isNewPage = false;
|
|
@@ -3845,16 +3990,39 @@ const runBrowserController = async (context, options)=>{
|
|
|
3845
3990
|
}
|
|
3846
3991
|
activeContainerPage = containerPage;
|
|
3847
3992
|
const dispatchRouter = createDispatchRouter();
|
|
3993
|
+
const headedReloadQueue = createHeadedSerialTaskQueue();
|
|
3994
|
+
let enqueueHeadedReload = async (_file, _testNamePattern)=>{
|
|
3995
|
+
throw new Error('Headed reload queue is not initialized');
|
|
3996
|
+
};
|
|
3997
|
+
const reloadTestFileWithTimeout = async (file, testNamePattern)=>{
|
|
3998
|
+
const timeoutMs = getHeadedPerFileTimeoutMs(file);
|
|
3999
|
+
let timeoutId;
|
|
4000
|
+
try {
|
|
4001
|
+
await Promise.race([
|
|
4002
|
+
rpcManager.reloadTestFile(file.testPath, testNamePattern),
|
|
4003
|
+
new Promise((_, reject)=>{
|
|
4004
|
+
timeoutId = setTimeout(()=>{
|
|
4005
|
+
reject(new Error(`Headed test execution timeout after ${timeoutMs / 1000}s for ${file.testPath}.`));
|
|
4006
|
+
}, timeoutMs);
|
|
4007
|
+
})
|
|
4008
|
+
]);
|
|
4009
|
+
} finally{
|
|
4010
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
4011
|
+
}
|
|
4012
|
+
};
|
|
3848
4013
|
const createRpcMethods = ()=>({
|
|
3849
4014
|
async rerunTest (testFile, testNamePattern) {
|
|
3850
4015
|
const projectName = context.normalizedConfig.name || 'project';
|
|
3851
4016
|
const relativePath = relative(context.rootPath, testFile);
|
|
3852
4017
|
const displayPath = `<${projectName}>/${relativePath}`;
|
|
3853
4018
|
logger.log(color.cyan(`\nRe-running test: ${displayPath}${testNamePattern ? ` (pattern: ${testNamePattern})` : ''}\n`));
|
|
3854
|
-
await
|
|
4019
|
+
await enqueueHeadedReload(getTestFileInfo(testFile), testNamePattern);
|
|
3855
4020
|
},
|
|
3856
4021
|
async getTestFiles () {
|
|
3857
|
-
return
|
|
4022
|
+
return currentTestFiles;
|
|
4023
|
+
},
|
|
4024
|
+
async onRunnerFramesReady (testFiles) {
|
|
4025
|
+
markRunnerFramesReady(testFiles);
|
|
3858
4026
|
},
|
|
3859
4027
|
async onTestFileStart (payload) {
|
|
3860
4028
|
await handleTestFileStart(payload);
|
|
@@ -3864,15 +4032,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
3864
4032
|
},
|
|
3865
4033
|
async onTestFileComplete (payload) {
|
|
3866
4034
|
await handleTestFileComplete(payload);
|
|
3867
|
-
completedTests++;
|
|
3868
|
-
if (completedTests >= allTestFiles.length && resolveAllTests) resolveAllTests();
|
|
3869
4035
|
},
|
|
3870
4036
|
async onLog (payload) {
|
|
3871
4037
|
await handleLog(payload);
|
|
3872
4038
|
},
|
|
3873
4039
|
async onFatal (payload) {
|
|
3874
4040
|
await handleFatal(payload);
|
|
3875
|
-
if (resolveAllTests) resolveAllTests();
|
|
3876
4041
|
},
|
|
3877
4042
|
async dispatch (request) {
|
|
3878
4043
|
return dispatchRouter.dispatch(request);
|
|
@@ -3895,21 +4060,21 @@ const runBrowserController = async (context, options)=>{
|
|
|
3895
4060
|
});
|
|
3896
4061
|
logger.log(color.cyan(`\nBrowser mode opened at http://localhost:${port}${pagePath}\n`));
|
|
3897
4062
|
}
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
timeoutId = setTimeout(()=>{
|
|
3903
|
-
logger.log(color.yellow(`\nTest execution timeout after ${totalTimeoutMs / 1000}s. Completed: ${completedTests}/${allTestFiles.length}\n`));
|
|
3904
|
-
resolve();
|
|
3905
|
-
}, totalTimeoutMs);
|
|
3906
|
-
});
|
|
4063
|
+
enqueueHeadedReload = async (file, testNamePattern)=>headedReloadQueue.enqueue(async ()=>{
|
|
4064
|
+
if (fatalError) return;
|
|
4065
|
+
await reloadTestFileWithTimeout(file, testNamePattern);
|
|
4066
|
+
});
|
|
3907
4067
|
const testStart = Date.now();
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
4068
|
+
try {
|
|
4069
|
+
await waitForRunnerFramesReady(currentTestFiles.map((file)=>file.testPath));
|
|
4070
|
+
for (const file of currentTestFiles){
|
|
4071
|
+
await enqueueHeadedReload(file);
|
|
4072
|
+
if (fatalError) break;
|
|
4073
|
+
}
|
|
4074
|
+
} catch (error) {
|
|
4075
|
+
fatalError = fatalError ?? toError(error);
|
|
4076
|
+
ensureProcessExitCode(1);
|
|
4077
|
+
}
|
|
3913
4078
|
const testTime = Date.now() - testStart;
|
|
3914
4079
|
if (isWatchMode) triggerRerun = async ()=>{
|
|
3915
4080
|
const newProjectEntries = await collectProjectEntries(context);
|
|
@@ -3923,7 +4088,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
3923
4088
|
const deletedTestPaths = collectDeletedTestPaths(watchContext.lastTestFiles, rerunPlan.currentTestFiles);
|
|
3924
4089
|
if (deletedTestPaths.length > 0) context.updateReporterResultState([], [], deletedTestPaths);
|
|
3925
4090
|
watchContext.lastTestFiles = rerunPlan.currentTestFiles;
|
|
3926
|
-
|
|
4091
|
+
currentTestFiles = rerunPlan.currentTestFiles;
|
|
4092
|
+
await rpcManager.notifyTestFileUpdate(currentTestFiles);
|
|
4093
|
+
await waitForRunnerFramesReady(currentTestFiles.map((file)=>file.testPath));
|
|
3927
4094
|
}
|
|
3928
4095
|
if (rerunPlan.normalizedAffectedTestFiles.length > 0) {
|
|
3929
4096
|
logger.log(color.cyan(`Re-running ${rerunPlan.normalizedAffectedTestFiles.length} affected test file(s)...\n`));
|
|
@@ -3932,7 +4099,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3932
4099
|
const fatalErrorBeforeRun = fatalError;
|
|
3933
4100
|
let rerunError;
|
|
3934
4101
|
try {
|
|
3935
|
-
for (const testFile of rerunPlan.normalizedAffectedTestFiles)await
|
|
4102
|
+
for (const testFile of rerunPlan.normalizedAffectedTestFiles)await enqueueHeadedReload(getTestFileInfo(testFile));
|
|
3936
4103
|
} catch (error) {
|
|
3937
4104
|
rerunError = toError(error);
|
|
3938
4105
|
throw error;
|
|
@@ -3952,8 +4119,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3952
4119
|
rerunFatalError
|
|
3953
4120
|
] : void 0
|
|
3954
4121
|
});
|
|
4122
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
3955
4123
|
}
|
|
3956
|
-
} else if (
|
|
4124
|
+
} else if (rerunPlan.filesChanged) logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
4125
|
+
else {
|
|
4126
|
+
logger.log(color.cyan('Tests will be re-executed automatically\n'));
|
|
4127
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
4128
|
+
}
|
|
3957
4129
|
};
|
|
3958
4130
|
const closeContainerRuntime = isWatchMode ? void 0 : async ()=>{
|
|
3959
4131
|
try {
|
|
@@ -3991,7 +4163,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3991
4163
|
}
|
|
3992
4164
|
if (isWatchMode && triggerRerun) {
|
|
3993
4165
|
watchContext.hooksEnabled = true;
|
|
3994
|
-
|
|
4166
|
+
logBrowserWatchReadyMessage(enableCliShortcuts);
|
|
3995
4167
|
}
|
|
3996
4168
|
return result;
|
|
3997
4169
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const isBrowserWatchCliShortcutsEnabled: () => boolean;
|
|
2
|
+
export declare const getBrowserWatchCliShortcutsHintMessage: () => string;
|
|
3
|
+
export declare const logBrowserWatchReadyMessage: (enableCliShortcuts: boolean) => void;
|
|
4
|
+
export declare function setupBrowserWatchCliShortcuts({ close, }: {
|
|
5
|
+
close: () => Promise<void>;
|
|
6
|
+
}): Promise<() => void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rstest/browser",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Browser mode support for Rstest testing framework.",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/web-infra-dev/rstest/issues"
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
44
44
|
"convert-source-map": "^2.0.0",
|
|
45
|
-
"open-editor": "^
|
|
45
|
+
"open-editor": "^6.0.0",
|
|
46
46
|
"pathe": "^2.0.3",
|
|
47
|
-
"sirv": "^
|
|
47
|
+
"sirv": "^3.0.2",
|
|
48
48
|
"ws": "^8.19.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@rslib/core": "
|
|
51
|
+
"@rslib/core": "0.20.0",
|
|
52
52
|
"@types/convert-source-map": "^2.0.3",
|
|
53
53
|
"@types/picomatch": "^4.0.2",
|
|
54
54
|
"@types/ws": "^8.18.1",
|
|
@@ -57,13 +57,13 @@
|
|
|
57
57
|
"picocolors": "^1.1.1",
|
|
58
58
|
"picomatch": "^4.0.3",
|
|
59
59
|
"playwright": "^1.58.2",
|
|
60
|
-
"@rstest/
|
|
60
|
+
"@rstest/core": "0.9.2",
|
|
61
61
|
"@rstest/tsconfig": "0.0.1",
|
|
62
|
-
"@rstest/
|
|
62
|
+
"@rstest/browser-ui": "0.0.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"playwright": "^1.49.1",
|
|
66
|
-
"@rstest/core": "^0.9.
|
|
66
|
+
"@rstest/core": "^0.9.2"
|
|
67
67
|
},
|
|
68
68
|
"peerDependenciesMeta": {
|
|
69
69
|
"playwright": {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type HeadedSerialTask = () => Promise<void>;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serializes headed browser file execution so only one task runs at a time.
|
|
5
|
+
* The queue keeps draining even if an earlier task rejects.
|
|
6
|
+
*/
|
|
7
|
+
export const createHeadedSerialTaskQueue = () => {
|
|
8
|
+
let queue: Promise<void> = Promise.resolve();
|
|
9
|
+
|
|
10
|
+
const enqueue = (task: HeadedSerialTask): Promise<void> => {
|
|
11
|
+
const next = queue.then(task);
|
|
12
|
+
queue = next.catch(() => {});
|
|
13
|
+
return next;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
enqueue,
|
|
18
|
+
};
|
|
19
|
+
};
|