qunitx-cli 0.10.0 → 0.11.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/cli.js +123 -33
- package/package.json +5 -4
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", () =>
|
|
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) => {
|
|
@@ -695,10 +701,34 @@ function setupWebServer(config, cachedContent) {
|
|
|
695
701
|
});
|
|
696
702
|
});
|
|
697
703
|
server.get("/tests.js", (_req, res) => {
|
|
704
|
+
const bytes = cachedContent.allTestCode?.length ?? null;
|
|
705
|
+
console.log(
|
|
706
|
+
`# [HTTPServer] GET /tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (allTestCode is null)"}`
|
|
707
|
+
);
|
|
708
|
+
if (bytes === null) {
|
|
709
|
+
res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
710
|
+
res.end(
|
|
711
|
+
'console.error("[qunitx] /tests.js requested before bundle was built \u2014 allTestCode is null");'
|
|
712
|
+
);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
config._onTestsJsServed?.();
|
|
698
716
|
res.writeHead(200, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
699
717
|
res.end(cachedContent.allTestCode);
|
|
700
718
|
});
|
|
701
719
|
server.get("/filtered-tests.js", (_req, res) => {
|
|
720
|
+
const bytes = cachedContent.filteredTestCode?.length ?? null;
|
|
721
|
+
console.log(
|
|
722
|
+
`# [HTTPServer] GET /filtered-tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (filteredTestCode is null)"}`
|
|
723
|
+
);
|
|
724
|
+
if (bytes === null) {
|
|
725
|
+
res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
726
|
+
res.end(
|
|
727
|
+
'console.error("[qunitx] /filtered-tests.js requested before bundle was built \u2014 filteredTestCode is null");'
|
|
728
|
+
);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
config._onTestsJsServed?.();
|
|
702
732
|
res.writeHead(200, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
703
733
|
res.end(cachedContent.filteredTestCode);
|
|
704
734
|
});
|
|
@@ -1002,7 +1032,9 @@ async function setupBrowser(config, cachedContent, existingBrowser = null) {
|
|
|
1002
1032
|
window.IS_PLAYWRIGHT = true;
|
|
1003
1033
|
});
|
|
1004
1034
|
page.on("console", async (msg) => {
|
|
1005
|
-
|
|
1035
|
+
const type = msg.type();
|
|
1036
|
+
const alwaysShow = type === "warning" || type === "error";
|
|
1037
|
+
if (!alwaysShow && !config.debug) return;
|
|
1006
1038
|
try {
|
|
1007
1039
|
const values = await Promise.all(msg.args().map((arg) => arg.jsonValue()));
|
|
1008
1040
|
console.log(...values);
|
|
@@ -1121,23 +1153,31 @@ import esbuild from "esbuild";
|
|
|
1121
1153
|
async function buildTestBundle(config, cachedContent) {
|
|
1122
1154
|
const { projectRoot, output } = config;
|
|
1123
1155
|
const allTestFilePaths = Object.keys(config.fsTree);
|
|
1156
|
+
if (allTestFilePaths.length === 0) {
|
|
1157
|
+
console.log("# [buildTestBundle] fsTree is empty \u2014 skipping build (no test files found)");
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const outfile = `${projectRoot}/${output}/tests.js`;
|
|
1124
1161
|
await Promise.all([
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1162
|
+
buildWithOverlayfsRetry(
|
|
1163
|
+
{
|
|
1164
|
+
stdin: {
|
|
1165
|
+
contents: allTestFilePaths.map((f) => `import "${f}";`).join(""),
|
|
1166
|
+
resolveDir: process.cwd()
|
|
1167
|
+
},
|
|
1168
|
+
bundle: true,
|
|
1169
|
+
logLevel: "error",
|
|
1170
|
+
outfile,
|
|
1171
|
+
keepNames: true,
|
|
1172
|
+
sourcemap: config.debug ? "inline" : config.watch ? "linked" : false,
|
|
1173
|
+
// Signal the runtime that all test modules are registered. The runtime's maybeStart()
|
|
1174
|
+
// waits for both this event and the WebSocket 'open' event before calling QUnit.start().
|
|
1175
|
+
// Dispatching from the bundle (rather than from a script onload attr) is reliable across
|
|
1176
|
+
// all browsers and does not require changes to user test code.
|
|
1177
|
+
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
1129
1178
|
},
|
|
1130
|
-
|
|
1131
|
-
|
|
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
|
-
}),
|
|
1179
|
+
outfile
|
|
1180
|
+
),
|
|
1141
1181
|
Promise.all(
|
|
1142
1182
|
cachedContent.htmlPathsToRunTests.map(async (htmlPath) => {
|
|
1143
1183
|
const targetPath = `${config.projectRoot}/${config.output}${htmlPath}`;
|
|
@@ -1148,7 +1188,7 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
1148
1188
|
})
|
|
1149
1189
|
)
|
|
1150
1190
|
]);
|
|
1151
|
-
cachedContent.allTestCode = await fs8.readFile(
|
|
1191
|
+
cachedContent.allTestCode = await fs8.readFile(outfile);
|
|
1152
1192
|
}
|
|
1153
1193
|
async function runTestsInBrowser(config, cachedContent = {}, connections, targetTestFilesToFilter) {
|
|
1154
1194
|
const { projectRoot, output } = config;
|
|
@@ -1162,6 +1202,9 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
1162
1202
|
if (!cachedContent.allTestCode) {
|
|
1163
1203
|
await buildTestBundle(config, cachedContent);
|
|
1164
1204
|
}
|
|
1205
|
+
if (!cachedContent.allTestCode) {
|
|
1206
|
+
return connections;
|
|
1207
|
+
}
|
|
1165
1208
|
if (runHasFilter) {
|
|
1166
1209
|
const outputPath = `${projectRoot}/${output}/filtered-tests.js`;
|
|
1167
1210
|
await buildFilteredTests(targetTestFilesToFilter, outputPath, config);
|
|
@@ -1204,17 +1247,42 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
1204
1247
|
return connections;
|
|
1205
1248
|
}
|
|
1206
1249
|
function buildFilteredTests(filteredTests, outputPath, config) {
|
|
1207
|
-
return
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1250
|
+
return buildWithOverlayfsRetry(
|
|
1251
|
+
{
|
|
1252
|
+
stdin: {
|
|
1253
|
+
contents: filteredTests.map((f) => `import "${f}";`).join(""),
|
|
1254
|
+
resolveDir: process.cwd()
|
|
1255
|
+
},
|
|
1256
|
+
bundle: true,
|
|
1257
|
+
logLevel: "error",
|
|
1258
|
+
outfile: outputPath,
|
|
1259
|
+
sourcemap: config.debug ? "inline" : config.watch ? "linked" : false,
|
|
1260
|
+
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
1211
1261
|
},
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1262
|
+
outputPath
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
async function buildWithOverlayfsRetry(options, outfile) {
|
|
1266
|
+
const RETRY_DELAY_MS = 100;
|
|
1267
|
+
const MAX_RETRIES = 3;
|
|
1268
|
+
const EMPTY_BUNDLE_THRESHOLD = 500;
|
|
1269
|
+
let result = await esbuild.build(options);
|
|
1270
|
+
for (let retry = 1; retry <= MAX_RETRIES; retry++) {
|
|
1271
|
+
const bytes2 = (await fs8.stat(outfile)).size;
|
|
1272
|
+
if (bytes2 >= EMPTY_BUNDLE_THRESHOLD) return result;
|
|
1273
|
+
console.log(
|
|
1274
|
+
`# [buildWithOverlayfsRetry] bundle is ${bytes2} bytes (< ${EMPTY_BUNDLE_THRESHOLD}) on attempt ${retry}/${MAX_RETRIES} \u2014 overlayfs flush race, retrying in ${RETRY_DELAY_MS}ms`
|
|
1275
|
+
);
|
|
1276
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
1277
|
+
result = await esbuild.build(options);
|
|
1278
|
+
}
|
|
1279
|
+
const bytes = (await fs8.stat(outfile)).size;
|
|
1280
|
+
if (bytes < EMPTY_BUNDLE_THRESHOLD) {
|
|
1281
|
+
console.log(
|
|
1282
|
+
`# [buildWithOverlayfsRetry] bundle is ${bytes} bytes after ${MAX_RETRIES} retries \u2014 proceeding`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
return result;
|
|
1218
1286
|
}
|
|
1219
1287
|
async function runTestInsideHTMLFile(filePath, { page, server, browser }, config) {
|
|
1220
1288
|
let QUNIT_RESULT;
|
|
@@ -1233,6 +1301,10 @@ async function runTestInsideHTMLFile(filePath, { page, server, browser }, config
|
|
|
1233
1301
|
clearTimeout(timeoutHandle);
|
|
1234
1302
|
timeoutHandle = setTimeout(resolveTestRace, config.timeout * 3);
|
|
1235
1303
|
};
|
|
1304
|
+
config._onTestsJsServed = () => {
|
|
1305
|
+
clearTimeout(timeoutHandle);
|
|
1306
|
+
timeoutHandle = setTimeout(resolveTestRace, config.timeout * 4);
|
|
1307
|
+
};
|
|
1236
1308
|
config._resetTestTimeout = () => {
|
|
1237
1309
|
wsConnected = true;
|
|
1238
1310
|
clearTimeout(timeoutHandle);
|
|
@@ -1256,6 +1328,7 @@ async function runTestInsideHTMLFile(filePath, { page, server, browser }, config
|
|
|
1256
1328
|
} finally {
|
|
1257
1329
|
clearTimeout(timeoutHandle);
|
|
1258
1330
|
config._onWsOpen = null;
|
|
1331
|
+
config._onTestsJsServed = null;
|
|
1259
1332
|
config._resetTestTimeout = null;
|
|
1260
1333
|
config._testRunDone = null;
|
|
1261
1334
|
}
|
|
@@ -1341,6 +1414,20 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
|
|
|
1341
1414
|
onFinishFunc
|
|
1342
1415
|
);
|
|
1343
1416
|
} catch {
|
|
1417
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1418
|
+
try {
|
|
1419
|
+
const s = await stat(fullPath);
|
|
1420
|
+
handleWatchEvent(
|
|
1421
|
+
config,
|
|
1422
|
+
extensions,
|
|
1423
|
+
s.isDirectory() ? "addDir" : "add",
|
|
1424
|
+
fullPath,
|
|
1425
|
+
onEventFunc,
|
|
1426
|
+
onFinishFunc
|
|
1427
|
+
);
|
|
1428
|
+
return;
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1344
1431
|
if (!(config.fsTree && fullPath in config.fsTree)) return;
|
|
1345
1432
|
handleWatchEvent(config, extensions, "unlink", fullPath, onEventFunc, onFinishFunc);
|
|
1346
1433
|
}
|
|
@@ -1794,7 +1881,7 @@ init_color();
|
|
|
1794
1881
|
var package_default = {
|
|
1795
1882
|
name: "qunitx-cli",
|
|
1796
1883
|
type: "module",
|
|
1797
|
-
version: "0.
|
|
1884
|
+
version: "0.11.0",
|
|
1798
1885
|
description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
1799
1886
|
author: "Izel Nakri",
|
|
1800
1887
|
license: "MIT",
|
|
@@ -1826,6 +1913,7 @@ var package_default = {
|
|
|
1826
1913
|
postinstall: "PLAYWRIGHT_SKIP_DOWNLOAD=true deno install --allow-scripts=npm:playwright-core || true",
|
|
1827
1914
|
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
1915
|
"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`,
|
|
1916
|
+
"test:release": "bash scripts/test-release.sh",
|
|
1829
1917
|
"test:sanity-first": "./cli.ts test/helpers/failing-tests.js test/helpers/failing-tests.ts",
|
|
1830
1918
|
"test:sanity-second": "./cli.ts test/helpers/passing-tests.js test/helpers/passing-tests.ts"
|
|
1831
1919
|
},
|
|
@@ -1841,15 +1929,15 @@ var package_default = {
|
|
|
1841
1929
|
url: "git+https://github.com/izelnakri/qunitx-cli.git"
|
|
1842
1930
|
},
|
|
1843
1931
|
dependencies: {
|
|
1844
|
-
esbuild: "^0.
|
|
1845
|
-
"playwright-core": "^1.
|
|
1932
|
+
esbuild: "^0.28.0",
|
|
1933
|
+
"playwright-core": "^1.59.1",
|
|
1846
1934
|
ws: "^8.20.0"
|
|
1847
1935
|
},
|
|
1848
1936
|
devDependencies: {
|
|
1849
1937
|
cors: "^2.8.6",
|
|
1850
1938
|
express: "^5.2.1",
|
|
1851
1939
|
"js-yaml": "^4.1.1",
|
|
1852
|
-
prettier: "^3.8.
|
|
1940
|
+
prettier: "^3.8.2",
|
|
1853
1941
|
qunitx: "^1.2.1",
|
|
1854
1942
|
typescript: "^6.0.2"
|
|
1855
1943
|
},
|
|
@@ -2214,7 +2302,9 @@ async function setupConfig() {
|
|
|
2214
2302
|
lastRanTestFiles: null,
|
|
2215
2303
|
COUNTER: { testCount: 0, failCount: 0, skipCount: 0, passCount: 0, errorCount: 0 },
|
|
2216
2304
|
_testRunDone: null,
|
|
2217
|
-
_resetTestTimeout: null
|
|
2305
|
+
_resetTestTimeout: null,
|
|
2306
|
+
_onWsOpen: null,
|
|
2307
|
+
_onTestsJsServed: null
|
|
2218
2308
|
};
|
|
2219
2309
|
config.htmlPaths = normalizeHTMLPaths(config.projectRoot, config.htmlPaths);
|
|
2220
2310
|
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.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"author": "Izel Nakri",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"postinstall": "PLAYWRIGHT_SKIP_DOWNLOAD=true deno install --allow-scripts=npm:playwright-core || true",
|
|
34
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
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",
|
|
36
|
+
"test:release": "bash scripts/test-release.sh",
|
|
36
37
|
"test:sanity-first": "./cli.ts test/helpers/failing-tests.js test/helpers/failing-tests.ts",
|
|
37
38
|
"test:sanity-second": "./cli.ts test/helpers/passing-tests.js test/helpers/passing-tests.ts"
|
|
38
39
|
},
|
|
@@ -48,15 +49,15 @@
|
|
|
48
49
|
"url": "git+https://github.com/izelnakri/qunitx-cli.git"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"esbuild": "^0.
|
|
52
|
-
"playwright-core": "^1.
|
|
52
|
+
"esbuild": "^0.28.0",
|
|
53
|
+
"playwright-core": "^1.59.1",
|
|
53
54
|
"ws": "^8.20.0"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"cors": "^2.8.6",
|
|
57
58
|
"express": "^5.2.1",
|
|
58
59
|
"js-yaml": "^4.1.1",
|
|
59
|
-
"prettier": "^3.8.
|
|
60
|
+
"prettier": "^3.8.2",
|
|
60
61
|
"qunitx": "^1.2.1",
|
|
61
62
|
"typescript": "^6.0.2"
|
|
62
63
|
},
|