@rstest/core 0.7.9 → 0.8.0

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/0~1472.js CHANGED
@@ -9,7 +9,7 @@ import { logger as logger_logger } from "./3278.js";
9
9
  const picocolors = __webpack_require__("../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
10
10
  var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
11
11
  async function loadBrowserModule(options = {}) {
12
- const coreVersion = "0.7.9";
12
+ const coreVersion = "0.8.0";
13
13
  const { projectRoots = [] } = options;
14
14
  let browserModule;
15
15
  let browserVersion;
package/dist/0~2173.js CHANGED
@@ -36,7 +36,7 @@ async function setupCliShortcuts({ closeServer, runAll, updateSnapshot, runFaile
36
36
  render();
37
37
  const onPromptKey = async (str, key)=>{
38
38
  if (!isPrompting) return;
39
- if (key.ctrl && 'c' === key.name) process.exit(0);
39
+ if (key.ctrl && 'c' === key.name) return void process.kill(process.pid, 'SIGINT');
40
40
  if ('return' === key.name || 'enter' === key.name) {
41
41
  process.stdin.off('keypress', onPromptKey);
42
42
  process.stdout.write('\n');
@@ -127,7 +127,7 @@ async function setupCliShortcuts({ closeServer, runAll, updateSnapshot, runFaile
127
127
  ];
128
128
  const handleKeypress = (str, key)=>{
129
129
  if (isPrompting) return;
130
- if (key.ctrl && 'c' === key.name) process.exit(0);
130
+ if (key.ctrl && 'c' === key.name) return void process.kill(process.pid, 'SIGINT');
131
131
  for (const shortcut of shortcuts)if (str === shortcut.key) {
132
132
  clearCurrentInputLine();
133
133
  shortcut.action();
@@ -150,19 +150,31 @@ async function setupCliShortcuts({ closeServer, runAll, updateSnapshot, runFaile
150
150
  rl.close();
151
151
  };
152
152
  }
153
+ async function runBrowserModeTests(context, browserProjects, options) {
154
+ const projectRoots = browserProjects.map((p)=>p.rootPath);
155
+ const { runBrowserTests } = await loadBrowserModule({
156
+ projectRoots
157
+ });
158
+ return runBrowserTests(context, options);
159
+ }
153
160
  async function runTests(context) {
154
161
  const browserProjects = context.projects.filter((project)=>project.normalizedConfig.browser.enabled);
155
162
  const nodeProjects = context.projects.filter((project)=>!project.normalizedConfig.browser.enabled);
156
163
  const hasBrowserTests = context.normalizedConfig.browser.enabled || browserProjects.length > 0;
157
164
  const hasNodeTests = nodeProjects.length > 0;
158
- if (hasBrowserTests) {
159
- const projectRoots = browserProjects.map((p)=>p.rootPath);
160
- const { runBrowserTests } = await loadBrowserModule({
161
- projectRoots
162
- });
163
- await runBrowserTests(context);
165
+ const isWatchMode = 'watch' === context.command;
166
+ const shouldUnifyReporter = !isWatchMode && hasBrowserTests && hasNodeTests;
167
+ if (hasBrowserTests && !hasNodeTests) return void await runBrowserModeTests(context, browserProjects, {
168
+ skipOnTestRunEnd: false
169
+ });
170
+ let browserResultPromise;
171
+ if (hasBrowserTests) browserResultPromise = runBrowserModeTests(context, browserProjects, {
172
+ skipOnTestRunEnd: shouldUnifyReporter
173
+ });
174
+ if (!hasNodeTests) {
175
+ if (browserResultPromise) await browserResultPromise;
176
+ return;
164
177
  }
165
- if (!hasNodeTests) return;
166
178
  const projects = nodeProjects;
167
179
  const { rootPath, reporters, snapshotManager, command, normalizedConfig: { coverage } } = context;
168
180
  const entriesCache = new Map();
@@ -200,7 +212,6 @@ async function runTests(context) {
200
212
  ];
201
213
  }));
202
214
  const rsbuildInstance = await prepareRsbuild(context, globTestSourceEntries, setupFiles, globalSetupFiles);
203
- const isWatchMode = 'watch' === command;
204
215
  const { getRsbuildStats, closeServer } = await createRsbuildServer({
205
216
  inspectedConfig: {
206
217
  ...context.normalizedConfig,
@@ -282,7 +293,12 @@ async function runTests(context) {
282
293
  }));
283
294
  const buildTime = testStart - buildStart;
284
295
  const testTime = Date.now() - testStart;
285
- const duration = {
296
+ const browserResult = browserResultPromise ? await browserResultPromise : void 0;
297
+ const duration = shouldUnifyReporter && browserResult ? {
298
+ totalTime: testTime + buildTime + browserResult.duration.totalTime,
299
+ buildTime: buildTime + browserResult.duration.buildTime,
300
+ testTime: testTime + browserResult.duration.testTime
301
+ } : {
286
302
  totalTime: testTime + buildTime,
287
303
  buildTime,
288
304
  testTime
@@ -291,6 +307,8 @@ async function runTests(context) {
291
307
  const testResults = returns.flatMap((r)=>r.testResults);
292
308
  const errors = returns.flatMap((r)=>r.errors || []);
293
309
  context.updateReporterResultState(results, testResults, currentDeletedEntries);
310
+ const nodeHasFailure = results.some((r)=>'fail' === r.status) || errors.length;
311
+ const browserHasFailure = shouldUnifyReporter && browserResult?.hasFailure;
294
312
  if (0 === results.length && !errors.length) {
295
313
  if ('watch' === command) if ('on-demand' === mode) logger_logger.log(picocolors_default().yellow('No test files need re-run.'));
296
314
  else logger_logger.log(picocolors_default().yellow('No test files found.'));
@@ -306,12 +324,13 @@ async function runTests(context) {
306
324
  logger_logger.log('');
307
325
  logger_logger.log(picocolors_default().gray('project:'), p.name);
308
326
  }
327
+ logger_logger.log(picocolors_default().gray('root:'), p.rootPath);
309
328
  logger_logger.log(picocolors_default().gray('include:'), p.normalizedConfig.include.join(picocolors_default().gray(', ')));
310
329
  logger_logger.log(picocolors_default().gray('exclude:'), p.normalizedConfig.exclude.patterns.join(picocolors_default().gray(', ')));
311
330
  });
312
331
  }
313
332
  }
314
- const isFailure = results.some((r)=>'fail' === r.status) || errors.length;
333
+ const isFailure = nodeHasFailure || browserHasFailure;
315
334
  if (isFailure) process.exitCode = 1;
316
335
  for (const reporter of reporters)await reporter.onTestRunEnd?.({
317
336
  results: context.reporterResults.results,
@@ -341,6 +360,25 @@ async function runTests(context) {
341
360
  };
342
361
  if ('watch' === command) {
343
362
  const enableCliShortcuts = isCliShortcutsEnabled();
363
+ let isCleaningUp = false;
364
+ const cleanup = async ()=>{
365
+ if (isCleaningUp) return;
366
+ isCleaningUp = true;
367
+ try {
368
+ await runGlobalTeardown();
369
+ await pool.close();
370
+ await closeServer();
371
+ } catch (error) {
372
+ logger_logger.log(picocolors_default().red(`Error during cleanup: ${error}`));
373
+ }
374
+ };
375
+ const handleSignal = async (signal)=>{
376
+ logger_logger.log(picocolors_default().yellow(`\nReceived ${signal}, cleaning up...`));
377
+ await cleanup();
378
+ process.exit('SIGINT' === signal ? 130 : 143);
379
+ };
380
+ process.on('SIGINT', handleSignal);
381
+ process.on('SIGTERM', handleSignal);
344
382
  const afterTestsWatchRun = ()=>{
345
383
  logger_logger.log(picocolors_default().green(' Waiting for file changes...'));
346
384
  if (enableCliShortcuts) if (snapshotManager.summary.unmatched) logger_logger.log(` ${picocolors_default().dim('press')} ${picocolors_default().yellow(picocolors_default().bold('u'))} ${picocolors_default().dim('to update snapshot')}${picocolors_default().dim(', press')} ${picocolors_default().bold('h')} ${picocolors_default().dim('to show help')}\n`);
@@ -435,6 +473,18 @@ async function runTests(context) {
435
473
  });
436
474
  } else {
437
475
  let isTeardown = false;
476
+ let isCleaningUp = false;
477
+ const cleanup = async ()=>{
478
+ if (isCleaningUp) return;
479
+ isCleaningUp = true;
480
+ try {
481
+ await runGlobalTeardown();
482
+ await pool.close();
483
+ await closeServer();
484
+ } catch (error) {
485
+ logger_logger.log(picocolors_default().red(`Error during cleanup: ${error}`));
486
+ }
487
+ };
438
488
  const unExpectedExit = (code)=>{
439
489
  if (isTeardown) logger_logger.log(picocolors_default().yellow(`Rstest exited unexpectedly with code ${code}, this is likely caused by test environment teardown.`));
440
490
  else {
@@ -445,13 +495,25 @@ async function runTests(context) {
445
495
  process.exitCode = 1;
446
496
  }
447
497
  };
498
+ const handleSignal = async (signal)=>{
499
+ logger_logger.log(picocolors_default().yellow(`\nReceived ${signal}, cleaning up...`));
500
+ await cleanup();
501
+ process.exit('SIGINT' === signal ? 130 : 143);
502
+ };
448
503
  process.on('exit', unExpectedExit);
449
- await run();
450
- isTeardown = true;
451
- await pool.close();
452
- await closeServer();
453
- await runGlobalTeardown();
454
- process.off('exit', unExpectedExit);
504
+ process.on('SIGINT', handleSignal);
505
+ process.on('SIGTERM', handleSignal);
506
+ try {
507
+ await run();
508
+ isTeardown = true;
509
+ await pool.close();
510
+ await closeServer();
511
+ await runGlobalTeardown();
512
+ } finally{
513
+ process.off('exit', unExpectedExit);
514
+ process.off('SIGINT', handleSignal);
515
+ process.off('SIGTERM', handleSignal);
516
+ }
455
517
  }
456
518
  }
457
519
  export { runTests };
package/dist/0~2255.js CHANGED
@@ -2,7 +2,7 @@ import 'module';
2
2
  /*#__PURE__*/ import.meta.url;
3
3
  import { __webpack_require__ } from "./rslib-runtime.js";
4
4
  import "./2672.js";
5
- const external_node_fs_ = __webpack_require__("fs");
5
+ const external_node_fs_ = __webpack_require__("node:fs");
6
6
  const pluginCoverageCore = (coverageOptions)=>({
7
7
  name: 'rstest:coverage-core',
8
8
  setup: (api)=>{
package/dist/0~4809.js CHANGED
@@ -6,8 +6,9 @@ const environment = {
6
6
  name: 'happy-dom',
7
7
  setup: async (global, options = {})=>{
8
8
  checkPkgInstalled('happy-dom');
9
- const { Window } = await import("happy-dom");
10
- const win = new Window({
9
+ const { Window, GlobalWindow } = await import("happy-dom");
10
+ const WindowClass = GlobalWindow || Window;
11
+ const win = new WindowClass({
11
12
  ...options,
12
13
  url: options.url || 'http://localhost:3000',
13
14
  console: console && global.console ? global.console : void 0
package/dist/0~7583.js CHANGED
@@ -222,7 +222,7 @@ function readdirp(root, options = {}) {
222
222
  options.root = root;
223
223
  return new ReaddirpStream(options);
224
224
  }
225
- const external_node_fs_ = __webpack_require__("fs");
225
+ const external_node_fs_ = __webpack_require__("node:fs");
226
226
  const external_node_os_ = __webpack_require__("node:os");
227
227
  const STR_DATA = 'data';
228
228
  const STR_END = 'end';
package/dist/0~7882.js CHANGED
@@ -36,7 +36,7 @@ __webpack_require__.add({
36
36
  });
37
37
  module.exports = __toCommonJS(src_exports);
38
38
  var import_promises = __webpack_require__("node:fs/promises");
39
- var import_node_fs = __webpack_require__("fs");
39
+ var import_node_fs = __webpack_require__("node:fs");
40
40
  const DEVIN_LOCAL_PATH = "/opt/.devin";
41
41
  const CURSOR = "cursor";
42
42
  const CURSOR_CLI = "cursor-cli";
@@ -272,7 +272,7 @@ async function handlePackageManager(filepath, options) {
272
272
  function isMetadataYarnClassic(metadataPath) {
273
273
  return metadataPath.endsWith(".yarn_integrity");
274
274
  }
275
- const external_node_fs_ = __webpack_require__("fs");
275
+ const external_node_fs_ = __webpack_require__("node:fs");
276
276
  function getUniqueBaseName(dir, baseName, ext) {
277
277
  const fullPath = external_node_path_["default"].join(dir, `${baseName}${ext}`);
278
278
  if (!external_node_fs_["default"].existsSync(fullPath)) return baseName;
@@ -803,6 +803,7 @@ function getConfigTemplate() {
803
803
  export default defineConfig({
804
804
  browser: {
805
805
  enabled: true,
806
+ provider: 'playwright',
806
807
  },
807
808
  });
808
809
  `;
@@ -1059,7 +1060,7 @@ async function createInteractive(cwd, projectInfo, isAgent) {
1059
1060
  }
1060
1061
  const provider = providerSelection;
1061
1062
  const preview = computeFilePreview(cwd, projectInfo);
1062
- const deps = getDependenciesWithVersions(effectiveFramework, provider, "0.7.9");
1063
+ const deps = getDependenciesWithVersions(effectiveFramework, provider, "0.8.0");
1063
1064
  const depsList = Object.entries(deps).map(([name, version])=>`${name}@${version}`).join(', ');
1064
1065
  const previewLines = [
1065
1066
  `${picocolors_default().cyan('+')} Create ${preview.configFile}`,
@@ -1137,7 +1138,7 @@ async function generateFiles(cwd, projectInfo, provider) {
1137
1138
  updatePackageJsonScripts(cwd, {
1138
1139
  'test:browser': 'rstest --config=rstest.browser.config.ts'
1139
1140
  });
1140
- const deps = getDependenciesWithVersions(effectiveFramework, provider, "0.7.9");
1141
+ const deps = getDependenciesWithVersions(effectiveFramework, provider, "0.8.0");
1141
1142
  updatePackageJsonDevDeps(cwd, deps);
1142
1143
  return createdFiles;
1143
1144
  }
package/dist/0~89.js CHANGED
@@ -223,7 +223,7 @@ const createPool = async ({ context, recommendWorkerCount = 1 / 0 })=>{
223
223
  };
224
224
  return {
225
225
  runTests: async ({ entries, getAssetFiles, getSourceMaps, setupEntries, project, updateSnapshot })=>{
226
- const projectName = context.normalizedConfig.name;
226
+ const projectName = project.name;
227
227
  const runtimeConfig = getRuntimeConfig(project);
228
228
  const setupAssets = setupEntries.flatMap((entry)=>entry.files || []);
229
229
  const results = await Promise.all(entries.map(async (entryInfo, index)=>{
@@ -402,6 +402,7 @@ async function runGlobalTeardown() {
402
402
  process.exitCode = 1;
403
403
  }
404
404
  }
405
+ const external_node_path_ = __webpack_require__("node:path");
405
406
  const RUNTIME_CHUNK_NAME = 'runtime';
406
407
  const requireShim = `// Rstest ESM shims
407
408
  import __rstest_shim_module__ from 'node:module';
@@ -447,7 +448,7 @@ const pluginBasic = (context)=>({
447
448
  },
448
449
  tools: {
449
450
  rspack: (config, { isProd, rspack })=>{
450
- config.context = rootPath;
451
+ config.context = external_node_path_["default"].resolve(rootPath);
451
452
  config.mode = isProd ? 'production' : 'development';
452
453
  config.output ??= {};
453
454
  config.output.iife = false;
@@ -517,7 +518,6 @@ const pluginBasic = (context)=>({
517
518
  });
518
519
  }
519
520
  });
520
- const external_node_path_ = __webpack_require__("node:path");
521
521
  const PLUGIN_CSS_FILTER = 'rstest:css-filter';
522
522
  const css_filter_dirname = external_node_path_["default"].dirname(fileURLToPath(import.meta.url));
523
523
  const pluginCSSFilter = ()=>({
@@ -679,7 +679,7 @@ const pluginInspect = ()=>enable ? {
679
679
  });
680
680
  }
681
681
  } : null;
682
- const external_node_fs_ = __webpack_require__("fs");
682
+ const external_node_fs_ = __webpack_require__("node:fs");
683
683
  const mockRuntime_dirname = external_node_path_["default"].dirname(fileURLToPath(import.meta.url));
684
684
  class MockRuntimeRspackPlugin {
685
685
  outputModule;
package/dist/0~9634.js CHANGED
@@ -5,7 +5,7 @@ import { getTaskNameWithPrefix, bgColor } from "./2672.js";
5
5
  import { prepareRsbuild, createPool, createRsbuildServer, runGlobalTeardown, runGlobalSetup } from "./0~89.js";
6
6
  import { getTestEntries, prettyTestPath, ROOT_SUITE_NAME } from "./1157.js";
7
7
  import { logger as logger_logger } from "./3278.js";
8
- const external_node_fs_ = __webpack_require__("fs");
8
+ const external_node_fs_ = __webpack_require__("node:fs");
9
9
  const external_node_path_ = __webpack_require__("node:path");
10
10
  const picocolors = __webpack_require__("../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
11
11
  var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
package/dist/1157.js CHANGED
@@ -1418,7 +1418,7 @@ __webpack_require__.add({
1418
1418
  }
1419
1419
  });
1420
1420
  const external_node_path_ = __webpack_require__("node:path");
1421
- const external_node_fs_ = __webpack_require__("fs");
1421
+ const external_node_fs_ = __webpack_require__("node:fs");
1422
1422
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
1423
1423
  function cleanPath(path) {
1424
1424
  let normalized = (0, external_node_path_.normalize)(path);
package/dist/2672.js CHANGED
@@ -72,7 +72,7 @@ __webpack_require__.add({
72
72
  module.exports = createColors();
73
73
  module.exports.createColors = createColors;
74
74
  },
75
- fs (module) {
75
+ "node:fs" (module) {
76
76
  module.exports = __rspack_external_node_fs_5ea92f0c;
77
77
  },
78
78
  "node:os" (module) {
@@ -534,7 +534,7 @@ const _path = {
534
534
  };
535
535
  const picocolors = __webpack_require__("../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
536
536
  var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
537
- const formatRootStr = (rootStr, root)=>rootStr.replace('<rootDir>', normalize(root));
537
+ const formatRootStr = (rootStr, root)=>rootStr.includes('<rootDir>') ? normalize(rootStr.replace('<rootDir>', normalize(root))) : rootStr;
538
538
  function getAbsolutePath(base, filepath) {
539
539
  return isAbsolute(filepath) ? filepath : join(base, filepath);
540
540
  }
package/dist/487.js CHANGED
@@ -39,7 +39,7 @@ __webpack_require__.add({
39
39
  var path = __webpack_require__("node:path");
40
40
  var fs;
41
41
  try {
42
- fs = __webpack_require__("fs");
42
+ fs = __webpack_require__("node:fs");
43
43
  if (!fs.existsSync || !fs.readFileSync) fs = null;
44
44
  } catch (err) {}
45
45
  var bufferFrom = __webpack_require__("../../node_modules/.pnpm/buffer-from@1.1.2/node_modules/buffer-from/index.js");
package/dist/6973.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath } from "./6198.js";
6
6
  import { rsbuild as __rspack_external__rsbuild_core_1b356efc } from "./4484.js";
7
7
  import { formatTestEntryName } from "./1157.js";
8
8
  import { posix } from "./3278.js";
9
- const external_node_fs_ = __webpack_require__("fs");
9
+ const external_node_fs_ = __webpack_require__("node:fs");
10
10
  const picocolors = __webpack_require__("../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
11
11
  var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
12
12
  const tryResolve = (request, rootPath)=>{
package/dist/9131.js CHANGED
@@ -504,13 +504,13 @@ function prepareCli() {
504
504
  if (!npm_execpath || npm_execpath.includes('npx-cli.js') || npm_execpath.includes('.bun')) logger_logger.log();
505
505
  }
506
506
  function showRstest() {
507
- logger_logger.greet(" Rstest v0.7.9");
507
+ logger_logger.greet(" Rstest v0.8.0");
508
508
  logger_logger.log('');
509
509
  }
510
510
  const applyCommonOptions = (cli)=>{
511
511
  cli.option('-c, --config <config>', 'Specify the configuration file, can be a relative or absolute path').option('--config-loader <loader>', 'Specify the loader to load the config file, can be `jiti` or `native`', {
512
512
  default: 'jiti'
513
- }).option('-r, --root <root>', 'Specify the project root directory, can be an absolute path or a path relative to cwd').option('--globals', 'Provide global APIs').option('--isolate', 'Run tests in an isolated environment').option('--include <include>', 'Match test files').option('--exclude <exclude>', 'Exclude files from test').option('-u, --update', 'Update snapshot files').option('--coverage', 'Enable code coverage collection').option('--project <name>', 'Run only projects that match the name, can be a full name or wildcards pattern').option('--passWithNoTests', 'Allows the test suite to pass when no files are found').option('--printConsoleTrace', 'Print console traces when calling any console method').option('--disableConsoleIntercept', 'Disable console intercept').option('--logHeapUsage', 'Log heap usage after each test').option('--slowTestThreshold <value>', 'The number of milliseconds after which a test or suite is considered slow').option('--reporter <reporter>', 'Specify the reporter to use').option('-t, --testNamePattern <value>', 'Run only tests with a name that matches the regex').option('--testEnvironment <name>', 'The environment that will be used for testing').option('--testTimeout <value>', 'Timeout of a test in milliseconds').option('--hookTimeout <value>', 'Timeout of hook in milliseconds').option('--hideSkippedTests', 'Hide skipped tests from the output').option('--retry <retry>', 'Number of times to retry a test if it fails').option('--bail [number]', 'Stop running tests after n failures. Set to 0 to run all tests regardless of failures').option('--maxConcurrency <value>', 'Maximum number of concurrent tests').option('--clearMocks', 'Automatically clear mock calls, instances, contexts and results before every test').option('--resetMocks', 'Automatically reset mock state before every test').option('--restoreMocks', 'Automatically restore mock state and implementation before every test').option('--browser', 'Run tests in browser mode (Chromium)').option('--unstubGlobals', 'Restores all global variables that were changed with `rstest.stubGlobal` before every test').option('--unstubEnvs', 'Restores all `process.env` values that were changed with `rstest.stubEnv` before every test').option('--includeTaskLocation', 'Collect test and suite locations. This might increase the running time.');
513
+ }).option('-r, --root <root>', 'Specify the project root directory, can be an absolute path or a path relative to cwd').option('--globals', 'Provide global APIs').option('--isolate', 'Run tests in an isolated environment').option('--include <include>', 'Match test files').option('--exclude <exclude>', 'Exclude files from test').option('-u, --update', 'Update snapshot files').option('--coverage', 'Enable code coverage collection').option('--project <name>', 'Run only projects that match the name, can be a full name or wildcards pattern').option('--passWithNoTests', 'Allows the test suite to pass when no files are found').option('--printConsoleTrace', 'Print console traces when calling any console method').option('--disableConsoleIntercept', 'Disable console intercept').option('--logHeapUsage', 'Log heap usage after each test').option('--slowTestThreshold <value>', 'The number of milliseconds after which a test or suite is considered slow').option('--reporter <reporter>', 'Specify the reporter to use').option('-t, --testNamePattern <value>', 'Run only tests with a name that matches the regex').option('--testEnvironment <name>', 'The environment that will be used for testing').option('--testTimeout <value>', 'Timeout of a test in milliseconds').option('--hookTimeout <value>', 'Timeout of hook in milliseconds').option('--hideSkippedTests', 'Hide skipped tests from the output').option('--hideSkippedTestFiles', 'Hide skipped test files from the output').option('--retry <retry>', 'Number of times to retry a test if it fails').option('--bail [number]', 'Stop running tests after n failures. Set to 0 to run all tests regardless of failures').option('--maxConcurrency <value>', 'Maximum number of concurrent tests').option('--clearMocks', 'Automatically clear mock calls, instances, contexts and results before every test').option('--resetMocks', 'Automatically reset mock state before every test').option('--restoreMocks', 'Automatically restore mock state and implementation before every test').option('--browser', 'Run tests in browser mode').option('--browser.enabled', 'Run tests in browser mode').option('--browser.name <name>', 'Browser to use: chromium, firefox, webkit (default: chromium)').option('--browser.headless', 'Run browser in headless mode (default: true in CI)').option('--browser.port <port>', 'Port for the browser mode dev server').option('--browser.strictPort', 'Exit if the specified port is already in use').option('--unstubGlobals', 'Restores all global variables that were changed with `rstest.stubGlobal` before every test').option('--unstubEnvs', 'Restores all `process.env` values that were changed with `rstest.stubEnv` before every test').option('--includeTaskLocation', 'Collect test and suite locations. This might increase the running time.');
514
514
  };
515
515
  const handleUnexpectedExit = (rstest, err)=>{
516
516
  for (const reporter of rstest?.context.reporters || [])reporter.onExit?.();
@@ -561,7 +561,7 @@ const runRest = async ({ options, filters, command })=>{
561
561
  function setupCommands() {
562
562
  const cli = dist('rstest');
563
563
  cli.help();
564
- cli.version("0.7.9");
564
+ cli.version("0.8.0");
565
565
  applyCommonOptions(cli);
566
566
  cli.command('[...filters]', 'run tests').option('-w, --watch', 'Run tests in watch mode').action(async (filters, options)=>{
567
567
  showRstest();
@@ -987,7 +987,7 @@ function G() {
987
987
  }
988
988
  const P = G();
989
989
  P?.name;
990
- const external_node_fs_ = __webpack_require__("fs");
990
+ const external_node_fs_ = __webpack_require__("node:fs");
991
991
  const picocolors = __webpack_require__("../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
992
992
  var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
993
993
  const findConfig = (basePath)=>DEFAULT_CONFIG_EXTENSIONS.map((ext)=>basePath + ext).find(external_node_fs_["default"].existsSync);
@@ -1098,6 +1098,7 @@ const createDefaultConfig = ()=>({
1098
1098
  snapshotFormat: {},
1099
1099
  env: {},
1100
1100
  hideSkippedTests: false,
1101
+ hideSkippedTestFiles: false,
1101
1102
  logHeapUsage: false,
1102
1103
  bail: 0,
1103
1104
  includeTaskLocation: false,
@@ -1105,7 +1106,8 @@ const createDefaultConfig = ()=>({
1105
1106
  enabled: false,
1106
1107
  provider: 'playwright',
1107
1108
  browser: 'chromium',
1108
- headless: T
1109
+ headless: T,
1110
+ strictPort: false
1109
1111
  },
1110
1112
  coverage: {
1111
1113
  exclude: [
@@ -1134,6 +1136,13 @@ const createDefaultConfig = ()=>({
1134
1136
  }
1135
1137
  });
1136
1138
  const withDefaultConfig = (config)=>{
1139
+ if (config.browser?.enabled === true) {
1140
+ if (!config.browser.provider) throw new Error('browser.provider is required when browser.enabled is true.');
1141
+ const supportedProviders = [
1142
+ 'playwright'
1143
+ ];
1144
+ if (!supportedProviders.includes(config.browser.provider)) throw new Error(`browser.provider must be one of: ${supportedProviders.join(', ')}.`);
1145
+ }
1137
1146
  const merged = mergeRstestConfig(createDefaultConfig(), config);
1138
1147
  merged.setupFiles = castArray(merged.setupFiles);
1139
1148
  merged.globalSetup = castArray(merged.globalSetup);
@@ -1151,7 +1160,8 @@ const withDefaultConfig = (config)=>{
1151
1160
  provider: merged.browser?.provider ?? 'playwright',
1152
1161
  browser: merged.browser?.browser ?? 'chromium',
1153
1162
  headless: merged.browser?.headless ?? T,
1154
- port: merged.browser?.port
1163
+ port: merged.browser?.port,
1164
+ strictPort: merged.browser?.strictPort ?? false
1155
1165
  };
1156
1166
  return {
1157
1167
  ...merged,
@@ -1187,6 +1197,7 @@ function mergeWithCLIOptions(config, options) {
1187
1197
  'disableConsoleIntercept',
1188
1198
  'testEnvironment',
1189
1199
  'hideSkippedTests',
1200
+ 'hideSkippedTestFiles',
1190
1201
  'logHeapUsage'
1191
1202
  ];
1192
1203
  for (const key of keys)if (void 0 !== options[key]) config[key] = options[key];
@@ -1198,6 +1209,19 @@ function mergeWithCLIOptions(config, options) {
1198
1209
  }
1199
1210
  if (options.exclude) config.exclude = castArray(options.exclude);
1200
1211
  if (options.include) config.include = castArray(options.include);
1212
+ if (void 0 !== options.browser) {
1213
+ config.browser ??= {
1214
+ provider: 'playwright'
1215
+ };
1216
+ if ('boolean' == typeof options.browser) config.browser.enabled = options.browser;
1217
+ else {
1218
+ if (void 0 !== options.browser.enabled) config.browser.enabled = options.browser.enabled;
1219
+ if (void 0 !== options.browser.name) config.browser.browser = options.browser.name;
1220
+ if (void 0 !== options.browser.headless) config.browser.headless = options.browser.headless;
1221
+ if (void 0 !== options.browser.port) config.browser.port = Number(options.browser.port);
1222
+ if (void 0 !== options.browser.strictPort) config.browser.strictPort = options.browser.strictPort;
1223
+ }
1224
+ }
1201
1225
  return config;
1202
1226
  }
1203
1227
  async function resolveConfig(options) {
@@ -1208,10 +1232,6 @@ async function resolveConfig(options) {
1208
1232
  });
1209
1233
  const mergedConfig = mergeWithCLIOptions(config, options);
1210
1234
  if (!mergedConfig.root) mergedConfig.root = options.cwd;
1211
- if (void 0 !== options.browser) {
1212
- config.browser ??= {};
1213
- config.browser.enabled = options.browser;
1214
- }
1215
1235
  return {
1216
1236
  config: mergedConfig,
1217
1237
  configFilePath: configFilePath ?? void 0
@@ -1339,6 +1359,7 @@ async function init_initCli(options) {
1339
1359
  };
1340
1360
  }
1341
1361
  async function runCLI() {
1362
+ process.title = 'rstest-node';
1342
1363
  prepareCli();
1343
1364
  try {
1344
1365
  setupCommands();
@@ -1662,11 +1683,17 @@ const statusStr = {
1662
1683
  todo: '-',
1663
1684
  skip: '-'
1664
1685
  };
1686
+ const statusColor = {
1687
+ fail: picocolors_default().red,
1688
+ pass: picocolors_default().green,
1689
+ todo: picocolors_default().gray,
1690
+ skip: picocolors_default().gray
1691
+ };
1665
1692
  const statusColorfulStr = {
1666
- fail: picocolors_default().red(statusStr.fail),
1667
- pass: picocolors_default().green(statusStr.pass),
1668
- todo: picocolors_default().gray(statusStr.todo),
1669
- skip: picocolors_default().gray(statusStr.skip)
1693
+ fail: statusColor.fail(statusStr.fail),
1694
+ pass: statusColor.pass(statusStr.pass),
1695
+ todo: statusColor.todo(statusStr.todo),
1696
+ skip: statusColor.skip(statusStr.skip)
1670
1697
  };
1671
1698
  const logCase = (result, options)=>{
1672
1699
  const isSlowCase = (result.duration || 0) > options.slowTestThreshold;
@@ -1680,8 +1707,10 @@ const logCase = (result, options)=>{
1680
1707
  if (result.errors) for (const error of result.errors)logger_logger.log(picocolors_default().red(` ${error.message}`));
1681
1708
  };
1682
1709
  const formatHeapUsed = (heap)=>`${Math.floor(heap / 1024 / 1024)} MB heap used`;
1683
- const logFileTitle = (test, relativePath, alwaysShowTime = false)=>{
1684
- let title = ` ${picocolors_default().bold(statusColorfulStr[test.status])} ${prettyTestPath(relativePath)}`;
1710
+ const logFileTitle = (test, relativePath, alwaysShowTime = false, showProjectName = false)=>{
1711
+ let title = ` ${picocolors_default().bold(statusColorfulStr[test.status])}`;
1712
+ if (showProjectName && test.project) title += ` ${statusColor[test.status](`[${test.project}]`)}`;
1713
+ title += ` ${prettyTestPath(relativePath)}`;
1685
1714
  const formatDuration = (duration)=>picocolors_default().green(prettyTime(duration));
1686
1715
  title += ` ${picocolors_default().gray(`(${test.results.length})`)}`;
1687
1716
  if (alwaysShowTime) title += ` ${formatDuration(test.duration)}`;
@@ -1699,16 +1728,21 @@ class DefaultReporter {
1699
1728
  this.config = config;
1700
1729
  this.options = options;
1701
1730
  this.testState = testState;
1702
- if (isTTY() || options.logger) this.statusRenderer = new StatusRenderer(rootPath, testState, options.logger);
1731
+ }
1732
+ ensureStatusRenderer() {
1733
+ if (this.statusRenderer) return;
1734
+ if (isTTY() || this.options.logger) this.statusRenderer = new StatusRenderer(this.rootPath, this.testState, this.options.logger);
1703
1735
  }
1704
1736
  onTestFileStart() {
1737
+ this.ensureStatusRenderer();
1705
1738
  this.statusRenderer?.onTestFileStart();
1706
1739
  }
1707
1740
  onTestFileResult(test) {
1708
1741
  this.statusRenderer?.onTestFileResult();
1742
+ if (this.config.hideSkippedTestFiles && 'skip' === test.status) return;
1709
1743
  const relativePath = relative(this.rootPath, test.testPath);
1710
1744
  const { slowTestThreshold } = this.config;
1711
- logFileTitle(test, relativePath);
1745
+ logFileTitle(test, relativePath, false, this.options.showProjectName);
1712
1746
  const showAllCases = this.testState.getTestFiles()?.length === 1;
1713
1747
  for (const result of test.results){
1714
1748
  const isDisplayed = showAllCases || 'fail' === result.status || (result.duration ?? 0) > slowTestThreshold || (result.retryCount ?? 0) > 0;
@@ -2222,7 +2256,13 @@ async function error_parseErrorStacktrace({ stack, getSourcemap, fullStack = isD
2222
2256
  if (!source) return null;
2223
2257
  return {
2224
2258
  ...frame,
2225
- file: isRelativePath(source) ? (0, external_node_path_.resolve)(frame.file, '../', source) : new URL(source).pathname,
2259
+ file: isRelativePath(source) ? (0, external_node_path_.resolve)(frame.file, '../', source) : (()=>{
2260
+ try {
2261
+ return new URL(source).pathname;
2262
+ } catch {
2263
+ return source;
2264
+ }
2265
+ })(),
2226
2266
  lineNumber: line,
2227
2267
  name,
2228
2268
  column
@@ -2353,11 +2393,25 @@ class JUnitReporter {
2353
2393
  }
2354
2394
  }
2355
2395
  class VerboseReporter extends DefaultReporter {
2396
+ verboseOptions = {};
2397
+ constructor({ rootPath, options, config, testState }){
2398
+ super({
2399
+ rootPath,
2400
+ options: {
2401
+ ...options,
2402
+ summary: true
2403
+ },
2404
+ config,
2405
+ testState
2406
+ });
2407
+ this.verboseOptions = options;
2408
+ }
2356
2409
  onTestFileResult(test) {
2357
2410
  this.statusRenderer?.onTestFileResult();
2411
+ if (this.config.hideSkippedTestFiles && 'skip' === test.status) return;
2358
2412
  const relativePath = relative(this.rootPath, test.testPath);
2359
2413
  const { slowTestThreshold } = this.config;
2360
- logFileTitle(test, relativePath, true);
2414
+ logFileTitle(test, relativePath, true, this.verboseOptions.showProjectName);
2361
2415
  for (const result of test.results)logCase(result, {
2362
2416
  slowTestThreshold,
2363
2417
  hideSkippedTests: this.config.hideSkippedTests
@@ -2462,17 +2516,11 @@ class Rstest {
2462
2516
  ...userConfig,
2463
2517
  root: rootPath
2464
2518
  });
2465
- const reporters = 'list' !== command ? createReporters(rstestConfig.reporters, {
2466
- rootPath,
2467
- config: rstestConfig,
2468
- testState: this.testState
2469
- }) : [];
2470
2519
  const snapshotManager = new SnapshotManager({
2471
2520
  updateSnapshot: rstestConfig.update ? 'all' : T ? 'none' : 'new'
2472
2521
  });
2473
- this.reporters = reporters;
2474
2522
  this.snapshotManager = snapshotManager;
2475
- this.version = "0.7.9";
2523
+ this.version = "0.8.0";
2476
2524
  this.rootPath = rootPath;
2477
2525
  this.originalConfig = userConfig;
2478
2526
  this.normalizedConfig = rstestConfig;
@@ -2508,6 +2556,15 @@ class Rstest {
2508
2556
  normalizedConfig: rstestConfig
2509
2557
  }
2510
2558
  ];
2559
+ const reporters = 'list' !== command ? createReporters(rstestConfig.reporters, {
2560
+ rootPath,
2561
+ config: rstestConfig,
2562
+ testState: this.testState,
2563
+ options: {
2564
+ showProjectName: projects.length > 1
2565
+ }
2566
+ }) : [];
2567
+ this.reporters = reporters;
2511
2568
  }
2512
2569
  updateReporterResultState(results, testResults, deletedEntries = []) {
2513
2570
  results.forEach((item)=>{
@@ -2531,7 +2588,7 @@ const reportersMap = {
2531
2588
  'github-actions': GithubActionsReporter,
2532
2589
  junit: JUnitReporter
2533
2590
  };
2534
- function createReporters(reporters, initOptions = {}) {
2591
+ function createReporters(reporters, initConfig = {}) {
2535
2592
  const result = castArray(reporters).map((reporter)=>{
2536
2593
  if ('string' == typeof reporter || Array.isArray(reporter)) {
2537
2594
  const [name, options = {}] = 'string' == typeof reporter ? [
@@ -2541,8 +2598,11 @@ function createReporters(reporters, initOptions = {}) {
2541
2598
  if (name in reportersMap) {
2542
2599
  const Reporter = reportersMap[name];
2543
2600
  return new Reporter({
2544
- ...initOptions,
2545
- options
2601
+ ...initConfig,
2602
+ options: {
2603
+ ...initConfig.options || {},
2604
+ ...options
2605
+ }
2546
2606
  });
2547
2607
  }
2548
2608
  throw new Error(`Reporter ${reporter} not found. Please install it or use a built-in reporter.`);