@rstest/browser 0.9.0 → 0.9.1

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/index.js CHANGED
@@ -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 registerWatchCleanup = ()=>{
2877
- if (watchContext.cleanupRegistered) return;
2878
- const cleanup = async ()=>{
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
- cleanup();
2983
+ cleanupWatchRuntime();
2888
2984
  });
2889
2985
  process.once('exit', ()=>{
2890
- cleanup();
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) return void logger.log(color.cyan('No affected browser test files detected, skipping re-run.\n'));
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
- logger.log(color.cyan('\nWatch mode enabled - will re-run tests on file changes\n'));
3910
+ logBrowserWatchReadyMessage(enableCliShortcuts);
3810
3911
  }
3811
3912
  return result;
3812
3913
  }
3813
- let completedTests = 0;
3814
- let resolveAllTests;
3815
- const allTestsPromise = new Promise((resolve)=>{
3816
- resolveAllTests = resolve;
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 rpcManager.reloadTestFile(testFile, testNamePattern);
4019
+ await enqueueHeadedReload(getTestFileInfo(testFile), testNamePattern);
3855
4020
  },
3856
4021
  async getTestFiles () {
3857
- return allTestFiles;
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
- const maxTestTimeout = Math.max(...browserProjects.map((p)=>p.normalizedConfig.testTimeout ?? 5000));
3899
- const totalTimeoutMs = maxTestTimeout * allTestFiles.length + 30000;
3900
- let timeoutId;
3901
- const testTimeout = new Promise((resolve)=>{
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
- await Promise.race([
3909
- allTestsPromise,
3910
- testTimeout
3911
- ]);
3912
- if (timeoutId) clearTimeout(timeoutId);
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
- await rpcManager.notifyTestFileUpdate(rerunPlan.currentTestFiles);
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 rpcManager.reloadTestFile(testFile);
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 (!rerunPlan.filesChanged) logger.log(color.cyan('Tests will be re-executed automatically\n'));
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
- logger.log(color.cyan('\nWatch mode enabled - will re-run tests on file changes\n'));
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.0",
3
+ "version": "0.9.1",
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,9 +42,9 @@
42
42
  "dependencies": {
43
43
  "@jridgewell/trace-mapping": "0.3.31",
44
44
  "convert-source-map": "^2.0.0",
45
- "open-editor": "^4.1.1",
45
+ "open-editor": "^6.0.0",
46
46
  "pathe": "^2.0.3",
47
- "sirv": "^2.0.4",
47
+ "sirv": "^3.0.2",
48
48
  "ws": "^8.19.0"
49
49
  },
50
50
  "devDependencies": {
@@ -57,13 +57,13 @@
57
57
  "picocolors": "^1.1.1",
58
58
  "picomatch": "^4.0.3",
59
59
  "playwright": "^1.58.2",
60
- "@rstest/browser-ui": "0.0.0",
60
+ "@rstest/core": "0.9.1",
61
61
  "@rstest/tsconfig": "0.0.1",
62
- "@rstest/core": "0.9.0"
62
+ "@rstest/browser-ui": "0.0.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "playwright": "^1.49.1",
66
- "@rstest/core": "^0.9.0"
66
+ "@rstest/core": "^0.9.1"
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
+ };