qunitx-cli 0.10.0 → 0.15.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.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/cli.js +182 -43
  3. package/package.json +9 -6
package/README.md CHANGED
@@ -188,6 +188,13 @@ All CLI flags can also be set in `package.json` under the `qunitx` key, so you d
188
188
 
189
189
  CLI flags always override `package.json` values when both are present.
190
190
 
191
+ ### Environment variables
192
+
193
+ | Variable | Description |
194
+ |------------------|---------------------------------------------------------------------------------------------------------------|
195
+ | `CHROME_BIN` | Path to the Chrome/Chromium executable. Required on systems where Chrome is not on `PATH` (e.g. many CI environments). Set automatically when using `browser-actions/setup-chrome` in GitHub Actions. |
196
+ | `QUNITX_BROWSER` | Browser engine to use (`chromium`, `firefox`, `webkit`). Equivalent to `--browser` on the CLI. Useful in CI matrix jobs. |
197
+
191
198
  If you do not provide any HTML template, qunitx falls back to its built-in `test/tests.html` boilerplate internally, so `qunitx init` is optional.
192
199
 
193
200
  You can also pass a custom HTML file on the CLI:
package/dist/cli.js CHANGED
@@ -129,7 +129,13 @@ var init_early_chrome = __esm({
129
129
  openWatchMode = openFromArgv && watchFromArgv;
130
130
  earlyChromeProcRef = null;
131
131
  if (!openWatchMode) {
132
- process.on("exit", () => earlyChromeProcRef?.kill());
132
+ process.on("exit", () => {
133
+ if (!earlyChromeProcRef) return;
134
+ try {
135
+ earlyChromeProcRef.kill("SIGKILL");
136
+ } catch {
137
+ }
138
+ });
133
139
  }
134
140
  perfLog("early-chrome.js: module evaluated");
135
141
  earlyBrowserPromise = isRunCommand && browserFromArgv === "chromium" ? findChrome().then((chromePath) => {
@@ -676,17 +682,38 @@ function setupWebServer(config, cachedContent) {
676
682
  socket.on("message", function message(data) {
677
683
  const { event, details, abort } = JSON.parse(data);
678
684
  if (event === "wsOpen") {
685
+ config._phase = "loading";
679
686
  config._onWsOpen?.();
680
687
  } else if (event === "connection") {
688
+ config._phase = "running";
681
689
  if (!config._groupMode) console.log("TAP version 13");
690
+ if (config.debug && config._groupMode) {
691
+ const allFiles = Object.keys(config.fsTree);
692
+ const relFiles = allFiles.map((f) => f.replace(`${config.projectRoot}/`, ""));
693
+ const shown = relFiles.slice(0, 2);
694
+ const rest = relFiles.length - shown.length;
695
+ const fileList = rest > 0 ? `${shown.join(" ")} +${rest} more` : shown.join(" ");
696
+ console.log("#", blue(`\u2500\u2500 ${fileList} \u2500\u2500`));
697
+ }
682
698
  config._resetTestTimeout?.();
683
699
  } else if (event === "testEnd" && !abort) {
684
700
  if (details.status === "failed") {
685
701
  config.lastFailedTestFiles = config.lastRanTestFiles;
686
702
  }
703
+ if (config.debug && details.runtime > config.timeout * 0.8) {
704
+ console.log(
705
+ `# SLOW (${details.runtime.toFixed(0)}ms / ${config.timeout}ms timeout): ${details.fullName.join(" | ")}`
706
+ );
707
+ }
687
708
  config._resetTestTimeout?.();
688
709
  TAPDisplayTestResult(config.COUNTER, details);
689
710
  } else if (event === "done") {
711
+ config._phase = "done";
712
+ if (config.debug && config._groupMode) {
713
+ console.log(
714
+ `# group done: ${details.passed} passed, ${details.failed} failed (${details.runtime}ms)`
715
+ );
716
+ }
690
717
  if (typeof config._testRunDone === "function") {
691
718
  config._testRunDone();
692
719
  config._testRunDone = null;
@@ -695,10 +722,34 @@ function setupWebServer(config, cachedContent) {
695
722
  });
696
723
  });
697
724
  server.get("/tests.js", (_req, res) => {
725
+ const bytes = cachedContent.allTestCode?.length ?? null;
726
+ console.log(
727
+ `# [HTTPServer] GET /tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (allTestCode is null)"}`
728
+ );
729
+ if (bytes === null) {
730
+ res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
731
+ res.end(
732
+ 'console.error("[qunitx] /tests.js requested before bundle was built \u2014 allTestCode is null");'
733
+ );
734
+ return;
735
+ }
736
+ config._onTestsJsServed?.();
698
737
  res.writeHead(200, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
699
738
  res.end(cachedContent.allTestCode);
700
739
  });
701
740
  server.get("/filtered-tests.js", (_req, res) => {
741
+ const bytes = cachedContent.filteredTestCode?.length ?? null;
742
+ console.log(
743
+ `# [HTTPServer] GET /filtered-tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (filteredTestCode is null)"}`
744
+ );
745
+ if (bytes === null) {
746
+ res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
747
+ res.end(
748
+ 'console.error("[qunitx] /filtered-tests.js requested before bundle was built \u2014 filteredTestCode is null");'
749
+ );
750
+ return;
751
+ }
752
+ config._onTestsJsServed?.();
702
753
  res.writeHead(200, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
703
754
  res.end(cachedContent.filteredTestCode);
704
755
  });
@@ -936,6 +987,7 @@ var init_web_server = __esm({
936
987
  init_find_internal_assets_from_html();
937
988
  init_html_content_marker();
938
989
  init_display_test_result();
990
+ init_color();
939
991
  init_path_exists();
940
992
  init_http();
941
993
  fsPromise = fs7.promises;
@@ -1002,7 +1054,9 @@ async function setupBrowser(config, cachedContent, existingBrowser = null) {
1002
1054
  window.IS_PLAYWRIGHT = true;
1003
1055
  });
1004
1056
  page.on("console", async (msg) => {
1005
- if (!config.debug) return;
1057
+ const type = msg.type();
1058
+ const alwaysShow = type === "warning" || type === "error";
1059
+ if (!alwaysShow && !config.debug) return;
1006
1060
  try {
1007
1061
  const values = await Promise.all(msg.args().map((arg) => arg.jsonValue()));
1008
1062
  console.log(...values);
@@ -1090,7 +1144,7 @@ async function runUserModule(modulePath, params, scriptPosition) {
1090
1144
  console.log("#", red(`QUnitX ${scriptPosition} script failed:`));
1091
1145
  console.trace(error);
1092
1146
  console.error(error);
1093
- return process.exit(1);
1147
+ process.stdout.write("", () => process.exit(1));
1094
1148
  }
1095
1149
  }
1096
1150
  var init_run_user_module = __esm({
@@ -1121,23 +1175,31 @@ import esbuild from "esbuild";
1121
1175
  async function buildTestBundle(config, cachedContent) {
1122
1176
  const { projectRoot, output } = config;
1123
1177
  const allTestFilePaths = Object.keys(config.fsTree);
1178
+ if (allTestFilePaths.length === 0) {
1179
+ console.log("# [buildTestBundle] fsTree is empty \u2014 skipping build (no test files found)");
1180
+ return;
1181
+ }
1182
+ const outfile = `${projectRoot}/${output}/tests.js`;
1124
1183
  await Promise.all([
1125
- esbuild.build({
1126
- stdin: {
1127
- contents: allTestFilePaths.map((f) => `import "${f}";`).join(""),
1128
- resolveDir: process.cwd()
1184
+ buildWithOverlayfsRetry(
1185
+ {
1186
+ stdin: {
1187
+ contents: allTestFilePaths.map((f) => `import "${f}";`).join(""),
1188
+ resolveDir: process.cwd()
1189
+ },
1190
+ bundle: true,
1191
+ logLevel: "error",
1192
+ outfile,
1193
+ keepNames: true,
1194
+ sourcemap: config.debug ? "inline" : config.watch ? "linked" : false,
1195
+ // Signal the runtime that all test modules are registered. The runtime's maybeStart()
1196
+ // waits for both this event and the WebSocket 'open' event before calling QUnit.start().
1197
+ // Dispatching from the bundle (rather than from a script onload attr) is reliable across
1198
+ // all browsers and does not require changes to user test code.
1199
+ footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
1129
1200
  },
1130
- bundle: true,
1131
- logLevel: "error",
1132
- outfile: `${projectRoot}/${output}/tests.js`,
1133
- keepNames: true,
1134
- sourcemap: config.debug ? "inline" : config.watch ? "linked" : false,
1135
- // Signal the runtime that all test modules are registered. The runtime's maybeStart()
1136
- // waits for both this event and the WebSocket 'open' event before calling QUnit.start().
1137
- // Dispatching from the bundle (rather than from a script onload attr) is reliable across
1138
- // all browsers and does not require changes to user test code.
1139
- footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
1140
- }),
1201
+ outfile
1202
+ ),
1141
1203
  Promise.all(
1142
1204
  cachedContent.htmlPathsToRunTests.map(async (htmlPath) => {
1143
1205
  const targetPath = `${config.projectRoot}/${config.output}${htmlPath}`;
@@ -1148,7 +1210,7 @@ async function buildTestBundle(config, cachedContent) {
1148
1210
  })
1149
1211
  )
1150
1212
  ]);
1151
- cachedContent.allTestCode = await fs8.readFile(`${projectRoot}/${output}/tests.js`);
1213
+ cachedContent.allTestCode = await fs8.readFile(outfile);
1152
1214
  }
1153
1215
  async function runTestsInBrowser(config, cachedContent = {}, connections, targetTestFilesToFilter) {
1154
1216
  const { projectRoot, output } = config;
@@ -1162,6 +1224,9 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
1162
1224
  if (!cachedContent.allTestCode) {
1163
1225
  await buildTestBundle(config, cachedContent);
1164
1226
  }
1227
+ if (!cachedContent.allTestCode) {
1228
+ return connections;
1229
+ }
1165
1230
  if (runHasFilter) {
1166
1231
  const outputPath = `${projectRoot}/${output}/filtered-tests.js`;
1167
1232
  await buildFilteredTests(targetTestFilesToFilter, outputPath, config);
@@ -1204,17 +1269,42 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
1204
1269
  return connections;
1205
1270
  }
1206
1271
  function buildFilteredTests(filteredTests, outputPath, config) {
1207
- return esbuild.build({
1208
- stdin: {
1209
- contents: filteredTests.map((f) => `import "${f}";`).join(""),
1210
- resolveDir: process.cwd()
1272
+ return buildWithOverlayfsRetry(
1273
+ {
1274
+ stdin: {
1275
+ contents: filteredTests.map((f) => `import "${f}";`).join(""),
1276
+ resolveDir: process.cwd()
1277
+ },
1278
+ bundle: true,
1279
+ logLevel: "error",
1280
+ outfile: outputPath,
1281
+ sourcemap: config.debug ? "inline" : config.watch ? "linked" : false,
1282
+ footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
1211
1283
  },
1212
- bundle: true,
1213
- logLevel: "error",
1214
- outfile: outputPath,
1215
- sourcemap: config.debug ? "inline" : config.watch ? "linked" : false,
1216
- footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
1217
- });
1284
+ outputPath
1285
+ );
1286
+ }
1287
+ async function buildWithOverlayfsRetry(options, outfile) {
1288
+ const RETRY_DELAY_MS = 100;
1289
+ const MAX_RETRIES = 3;
1290
+ const EMPTY_BUNDLE_THRESHOLD = 500;
1291
+ let result = await esbuild.build(options);
1292
+ for (let retry = 1; retry <= MAX_RETRIES; retry++) {
1293
+ const bytes2 = (await fs8.stat(outfile)).size;
1294
+ if (bytes2 >= EMPTY_BUNDLE_THRESHOLD) return result;
1295
+ console.log(
1296
+ `# [buildWithOverlayfsRetry] bundle is ${bytes2} bytes (< ${EMPTY_BUNDLE_THRESHOLD}) on attempt ${retry}/${MAX_RETRIES} \u2014 overlayfs flush race, retrying in ${RETRY_DELAY_MS}ms`
1297
+ );
1298
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
1299
+ result = await esbuild.build(options);
1300
+ }
1301
+ const bytes = (await fs8.stat(outfile)).size;
1302
+ if (bytes < EMPTY_BUNDLE_THRESHOLD) {
1303
+ console.log(
1304
+ `# [buildWithOverlayfsRetry] bundle is ${bytes} bytes after ${MAX_RETRIES} retries \u2014 proceeding`
1305
+ );
1306
+ }
1307
+ return result;
1218
1308
  }
1219
1309
  async function runTestInsideHTMLFile(filePath, { page, server, browser }, config) {
1220
1310
  let QUNIT_RESULT;
@@ -1233,6 +1323,10 @@ async function runTestInsideHTMLFile(filePath, { page, server, browser }, config
1233
1323
  clearTimeout(timeoutHandle);
1234
1324
  timeoutHandle = setTimeout(resolveTestRace, config.timeout * 3);
1235
1325
  };
1326
+ config._onTestsJsServed = () => {
1327
+ clearTimeout(timeoutHandle);
1328
+ timeoutHandle = setTimeout(resolveTestRace, config.timeout * 4);
1329
+ };
1236
1330
  config._resetTestTimeout = () => {
1237
1331
  wsConnected = true;
1238
1332
  clearTimeout(timeoutHandle);
@@ -1256,6 +1350,7 @@ async function runTestInsideHTMLFile(filePath, { page, server, browser }, config
1256
1350
  } finally {
1257
1351
  clearTimeout(timeoutHandle);
1258
1352
  config._onWsOpen = null;
1353
+ config._onTestsJsServed = null;
1259
1354
  config._resetTestTimeout = null;
1260
1355
  config._testRunDone = null;
1261
1356
  }
@@ -1341,6 +1436,20 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
1341
1436
  onFinishFunc
1342
1437
  );
1343
1438
  } catch {
1439
+ await new Promise((resolve) => setTimeout(resolve, 50));
1440
+ try {
1441
+ const s = await stat(fullPath);
1442
+ handleWatchEvent(
1443
+ config,
1444
+ extensions,
1445
+ s.isDirectory() ? "addDir" : "add",
1446
+ fullPath,
1447
+ onEventFunc,
1448
+ onFinishFunc
1449
+ );
1450
+ return;
1451
+ } catch {
1452
+ }
1344
1453
  if (!(config.fsTree && fullPath in config.fsTree)) return;
1345
1454
  handleWatchEvent(config, extensions, "unlink", fullPath, onEventFunc, onFinishFunc);
1346
1455
  }
@@ -1383,6 +1492,7 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
1383
1492
  function handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFinishFunc) {
1384
1493
  const isFileEvent = extensions.some((ext) => filePath.endsWith(`.${ext}`));
1385
1494
  if (!isFileEvent && event !== "unlinkDir") return;
1495
+ if (event === "change" && config._building && config._justAddedFiles?.has(filePath)) return;
1386
1496
  mutateFSTree(config.fsTree, event, filePath);
1387
1497
  console.log(
1388
1498
  "#",
@@ -1395,6 +1505,7 @@ function handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFi
1395
1505
  );
1396
1506
  if (!config._building) {
1397
1507
  config._building = true;
1508
+ config._justAddedFiles = event === "add" ? /* @__PURE__ */ new Set([filePath]) : /* @__PURE__ */ new Set();
1398
1509
  const result = onEventFunc(event, filePath);
1399
1510
  if (!(result instanceof Promise)) {
1400
1511
  config._building = false;
@@ -1413,6 +1524,7 @@ function handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFi
1413
1524
  }
1414
1525
  });
1415
1526
  } else {
1527
+ if (event === "add") config._justAddedFiles?.add(filePath);
1416
1528
  config._pendingBuildTrigger = () => handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFinishFunc);
1417
1529
  }
1418
1530
  }
@@ -1507,7 +1619,7 @@ function setupKeyboardEvents(config, cachedContent, connections) {
1507
1619
  });
1508
1620
  }
1509
1621
  function abortBrowserQUnit(_config, connections) {
1510
- connections.server.publish("abort", "abort");
1622
+ connections.server.publish("abort");
1511
1623
  }
1512
1624
  var init_keyboard_events = __esm({
1513
1625
  "lib/setup/keyboard-events.ts"() {
@@ -1584,11 +1696,21 @@ async function run(config) {
1584
1696
  if (["change", "unlink", "unlinkDir"].includes(event)) {
1585
1697
  if (event === "change" && !(file in config.fsTree)) return;
1586
1698
  cachedContent.allTestCode = null;
1699
+ if (config.debug) {
1700
+ console.log(
1701
+ `# Rerun triggered: ${event} \u2192 ${file.replace(`${config.projectRoot}/`, "")}`
1702
+ );
1703
+ }
1587
1704
  return await runTestsInBrowser(config, cachedContent, connections);
1588
1705
  }
1706
+ if (config.debug) {
1707
+ console.log(
1708
+ `# Rerun triggered: ${event} \u2192 ${file.replace(`${config.projectRoot}/`, "")}`
1709
+ );
1710
+ }
1589
1711
  await runTestsInBrowser(config, cachedContent, connections, [file]);
1590
1712
  },
1591
- (_path, _event) => connections.server.publish("refresh", "refresh")
1713
+ (_path, _event) => connections.server.publish("refresh")
1592
1714
  );
1593
1715
  await watcherReady;
1594
1716
  }
@@ -1604,10 +1726,14 @@ async function run(config) {
1604
1726
  fsTree: Object.fromEntries(groupFiles.map((f) => [f, config.fsTree[f]])),
1605
1727
  // Single group keeps the root output dir for backward-compatible file paths.
1606
1728
  output: groupCount === 1 ? config.output : `${config.output}/group-${i}`,
1607
- _groupMode: true
1729
+ _groupMode: true,
1730
+ _phase: "bundling"
1608
1731
  }));
1609
1732
  const groupCachedContents = groups.map(() => ({ ...cachedContent }));
1610
1733
  console.log("TAP version 13");
1734
+ console.log(
1735
+ `# Running ${allFiles.length} test file${allFiles.length === 1 ? "" : "s"} across ${groupCount} group${groupCount === 1 ? "" : "s"}`
1736
+ );
1611
1737
  const [browser] = await Promise.all([
1612
1738
  launchBrowser(config),
1613
1739
  Promise.all(
@@ -1629,14 +1755,22 @@ async function run(config) {
1629
1755
  const groupResults = await Promise.allSettled(
1630
1756
  groupConfigs.map((groupConfig, i) => {
1631
1757
  const groupTimeout = new Promise((_, reject) => {
1632
- const t = setTimeout(
1633
- () => reject(new Error(`Group ${i} timed out after ${GROUP_TIMEOUT_MS}ms`)),
1634
- GROUP_TIMEOUT_MS
1635
- );
1758
+ const t = setTimeout(() => {
1759
+ const files = Object.keys(groupConfig.fsTree).map(
1760
+ (f) => f.replace(`${groupConfig.projectRoot}/`, "")
1761
+ );
1762
+ reject(
1763
+ new Error(
1764
+ `Group ${i} timed out after ${GROUP_TIMEOUT_MS / 1e3}s in phase '${groupConfig._phase ?? "unknown"}'
1765
+ Files: ${files.join(", ")}`
1766
+ )
1767
+ );
1768
+ }, GROUP_TIMEOUT_MS);
1636
1769
  t.unref();
1637
1770
  });
1638
1771
  return Promise.race([
1639
1772
  (async () => {
1773
+ groupConfig._phase = "connecting";
1640
1774
  const connections = await setupBrowser(groupConfig, groupCachedContents[i], browser);
1641
1775
  groupConfig.expressApp = connections.server;
1642
1776
  if (config.before) {
@@ -1794,7 +1928,7 @@ init_color();
1794
1928
  var package_default = {
1795
1929
  name: "qunitx-cli",
1796
1930
  type: "module",
1797
- version: "0.10.0",
1931
+ version: "0.15.0",
1798
1932
  description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
1799
1933
  author: "Izel Nakri",
1800
1934
  license: "MIT",
@@ -1824,8 +1958,11 @@ var package_default = {
1824
1958
  "changelog:preview": "git-cliff",
1825
1959
  "changelog:update": "git-cliff --output CHANGELOG.md",
1826
1960
  postinstall: "PLAYWRIGHT_SKIP_DOWNLOAD=true deno install --allow-scripts=npm:playwright-core || true",
1827
- test: `node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test --test-concurrency=$(node -p 'require("os").availableParallelism()') test/**/*-test.ts`,
1828
- "test:browser": `node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test --test-concurrency=$(node -p 'require("os").availableParallelism()') test/flags/*-test.ts test/inputs/*-test.ts`,
1961
+ test: "node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test test/**/*-test.ts",
1962
+ "test:debug": "node --experimental-strip-types test/setup.ts && QUNITX_DEBUG=1 node --experimental-strip-types --test test/**/*-test.ts",
1963
+ dev: "node --experimental-strip-types test/setup.ts && node --experimental-strip-types --test --watch test/**/*-test.ts",
1964
+ "test:browser": "node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test test/flags/*-test.ts test/inputs/*-test.ts",
1965
+ "test:release": "bash scripts/test-release.sh",
1829
1966
  "test:sanity-first": "./cli.ts test/helpers/failing-tests.js test/helpers/failing-tests.ts",
1830
1967
  "test:sanity-second": "./cli.ts test/helpers/passing-tests.js test/helpers/passing-tests.ts"
1831
1968
  },
@@ -1841,15 +1978,15 @@ var package_default = {
1841
1978
  url: "git+https://github.com/izelnakri/qunitx-cli.git"
1842
1979
  },
1843
1980
  dependencies: {
1844
- esbuild: "^0.27.3",
1845
- "playwright-core": "^1.58.2",
1981
+ esbuild: "^0.28.0",
1982
+ "playwright-core": "^1.59.1",
1846
1983
  ws: "^8.20.0"
1847
1984
  },
1848
1985
  devDependencies: {
1849
1986
  cors: "^2.8.6",
1850
1987
  express: "^5.2.1",
1851
1988
  "js-yaml": "^4.1.1",
1852
- prettier: "^3.8.1",
1989
+ prettier: "^3.8.2",
1853
1990
  qunitx: "^1.2.1",
1854
1991
  typescript: "^6.0.2"
1855
1992
  },
@@ -2214,7 +2351,9 @@ async function setupConfig() {
2214
2351
  lastRanTestFiles: null,
2215
2352
  COUNTER: { testCount: 0, failCount: 0, skipCount: 0, passCount: 0, errorCount: 0 },
2216
2353
  _testRunDone: null,
2217
- _resetTestTimeout: null
2354
+ _resetTestTimeout: null,
2355
+ _onWsOpen: null,
2356
+ _onTestsJsServed: null
2218
2357
  };
2219
2358
  config.htmlPaths = normalizeHTMLPaths(config.projectRoot, config.htmlPaths);
2220
2359
  config.fsTree = await buildFSTree(config.testFileLookupPaths, config);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx-cli",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.15.0",
5
5
  "description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",
@@ -31,8 +31,11 @@
31
31
  "changelog:preview": "git-cliff",
32
32
  "changelog:update": "git-cliff --output CHANGELOG.md",
33
33
  "postinstall": "PLAYWRIGHT_SKIP_DOWNLOAD=true deno install --allow-scripts=npm:playwright-core || true",
34
- "test": "node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test --test-concurrency=$(node -p 'require(\"os\").availableParallelism()') test/**/*-test.ts",
35
- "test:browser": "node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test --test-concurrency=$(node -p 'require(\"os\").availableParallelism()') test/flags/*-test.ts test/inputs/*-test.ts",
34
+ "test": "node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test test/**/*-test.ts",
35
+ "test:debug": "node --experimental-strip-types test/setup.ts && QUNITX_DEBUG=1 node --experimental-strip-types --test test/**/*-test.ts",
36
+ "dev": "node --experimental-strip-types test/setup.ts && node --experimental-strip-types --test --watch test/**/*-test.ts",
37
+ "test:browser": "node --experimental-strip-types test/setup.ts && FORCE_COLOR=0 node --experimental-strip-types --test test/flags/*-test.ts test/inputs/*-test.ts",
38
+ "test:release": "bash scripts/test-release.sh",
36
39
  "test:sanity-first": "./cli.ts test/helpers/failing-tests.js test/helpers/failing-tests.ts",
37
40
  "test:sanity-second": "./cli.ts test/helpers/passing-tests.js test/helpers/passing-tests.ts"
38
41
  },
@@ -48,15 +51,15 @@
48
51
  "url": "git+https://github.com/izelnakri/qunitx-cli.git"
49
52
  },
50
53
  "dependencies": {
51
- "esbuild": "^0.27.3",
52
- "playwright-core": "^1.58.2",
54
+ "esbuild": "^0.28.0",
55
+ "playwright-core": "^1.59.1",
53
56
  "ws": "^8.20.0"
54
57
  },
55
58
  "devDependencies": {
56
59
  "cors": "^2.8.6",
57
60
  "express": "^5.2.1",
58
61
  "js-yaml": "^4.1.1",
59
- "prettier": "^3.8.1",
62
+ "prettier": "^3.8.2",
60
63
  "qunitx": "^1.2.1",
61
64
  "typescript": "^6.0.2"
62
65
  },