@jsenv/test 3.2.1 → 3.3.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/jsenv_test.js +471 -160
- package/package.json +7 -7
- package/src/execution/execute_test_plan.js +156 -64
- package/src/execution/format_error_for_terminal.js +2 -51
- package/src/execution/is_inside_fragment.js +32 -0
- package/src/execution/junit_xml_file/report_as_junit_xml.js +10 -6
- package/src/execution/replace_urls.js +50 -0
- package/src/execution/report_as_json.js +121 -0
- package/src/execution/reporters/reporter_list.js +91 -24
- package/src/main.js +1 -0
- package/src/runtime_browsers/firefox.js +2 -1
- package/src/runtime_node/node_child_process.js +3 -7
- package/src/runtime_node/node_worker_thread.js +3 -6
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { assertAndNormalizeFileUrl, writeFileSync } from "@jsenv/filesystem";
|
|
2
|
+
import { urlToFileSystemPath } from "@jsenv/urls";
|
|
3
|
+
|
|
4
|
+
import { replaceUrls } from "./replace_urls.js";
|
|
5
|
+
|
|
6
|
+
export const reportAsJson = (
|
|
7
|
+
testPlanResult,
|
|
8
|
+
fileUrl,
|
|
9
|
+
{ mockFluctuatingValues } = {},
|
|
10
|
+
) => {
|
|
11
|
+
fileUrl = assertAndNormalizeFileUrl(fileUrl);
|
|
12
|
+
|
|
13
|
+
if (!mockFluctuatingValues) {
|
|
14
|
+
writeFileSync(fileUrl, JSON.stringify(testPlanResult, null, " "));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const testPlanResultCopy = deepCopy(testPlanResult, {});
|
|
18
|
+
testPlanResultCopy.os.name = "<mock>";
|
|
19
|
+
testPlanResultCopy.os.version = "<mock>";
|
|
20
|
+
testPlanResultCopy.os.availableCpu = "<mock>";
|
|
21
|
+
testPlanResultCopy.os.availableMemory = "<mock>";
|
|
22
|
+
testPlanResultCopy.process.version = "<mock>";
|
|
23
|
+
testPlanResultCopy.memoryUsage = "<mock>";
|
|
24
|
+
testPlanResultCopy.cpuUsage = "<mock>";
|
|
25
|
+
testPlanResultCopy.rootDirectoryUrl = "/mock/";
|
|
26
|
+
testPlanResultCopy.timings = "<mock>";
|
|
27
|
+
for (const group of Object.keys(testPlanResultCopy.groups)) {
|
|
28
|
+
testPlanResultCopy.groups[group].runtimeVersion = "<mock>";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const relativeUrl of Object.keys(testPlanResultCopy.results)) {
|
|
32
|
+
const fileResults = testPlanResultCopy.results[relativeUrl];
|
|
33
|
+
for (const groupName of Object.keys(fileResults)) {
|
|
34
|
+
const executionResult = fileResults[groupName];
|
|
35
|
+
const { consoleCalls } = executionResult;
|
|
36
|
+
if (consoleCalls) {
|
|
37
|
+
for (const consoleCall of consoleCalls) {
|
|
38
|
+
consoleCall.text = mockFluctuatingValuesInMessage(consoleCall.text, {
|
|
39
|
+
rootDirectoryUrl: testPlanResult.rootDirectoryUrl,
|
|
40
|
+
});
|
|
41
|
+
consoleCall.text = consoleCall.text.replace(/\r\n/g, "\n");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const { errors } = executionResult;
|
|
45
|
+
if (errors) {
|
|
46
|
+
for (const error of errors) {
|
|
47
|
+
if (error.message) {
|
|
48
|
+
error.message = mockFluctuatingValuesInMessage(error.message, {
|
|
49
|
+
rootDirectoryUrl: testPlanResult.rootDirectoryUrl,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (error.stack) {
|
|
53
|
+
error.stack = mockFluctuatingValuesInMessage(error.stack, {
|
|
54
|
+
rootDirectoryUrl: testPlanResult.rootDirectoryUrl,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (executionResult.timings) {
|
|
60
|
+
executionResult.timings = "<mock>";
|
|
61
|
+
}
|
|
62
|
+
if (Object.hasOwn(executionResult, "memoryUsage")) {
|
|
63
|
+
executionResult.memoryUsage = "<mock>";
|
|
64
|
+
}
|
|
65
|
+
if (executionResult.performance) {
|
|
66
|
+
executionResult.performance = "<mock>";
|
|
67
|
+
}
|
|
68
|
+
if (executionResult.coverageFileUrl) {
|
|
69
|
+
executionResult.coverageFileUrl = "<mock>";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
writeFileSync(fileUrl, JSON.stringify(testPlanResultCopy, null, " "));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const mockFluctuatingValuesInMessage = (message, { rootDirectoryUrl }) => {
|
|
77
|
+
return replaceUrls(message, ({ url, line, column }) => {
|
|
78
|
+
let urlAsPath = String(url).startsWith("file:")
|
|
79
|
+
? urlToFileSystemPath(url)
|
|
80
|
+
: url;
|
|
81
|
+
const rootDirectoryPath = urlToFileSystemPath(rootDirectoryUrl);
|
|
82
|
+
urlAsPath = urlAsPath.replace(rootDirectoryPath, "/mock/");
|
|
83
|
+
if (process.platform === "win32") {
|
|
84
|
+
urlAsPath = urlAsPath.replace(/\\/g, "/");
|
|
85
|
+
}
|
|
86
|
+
if (typeof line === "number" && typeof column === "number") {
|
|
87
|
+
return `${urlAsPath}:${line}:${column}`;
|
|
88
|
+
}
|
|
89
|
+
if (typeof line === "number") {
|
|
90
|
+
return `${urlAsPath}:${line}`;
|
|
91
|
+
}
|
|
92
|
+
return urlAsPath;
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const deepCopy = (from, into) => {
|
|
97
|
+
const copyValue = (value) => {
|
|
98
|
+
if (value === null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (typeof value === "object") {
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
return deepCopy(value, []);
|
|
104
|
+
}
|
|
105
|
+
return deepCopy(value, {});
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(from)) {
|
|
111
|
+
let i = 0;
|
|
112
|
+
while (i < from.length) {
|
|
113
|
+
into[i] = copyValue(from[i]);
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const key of Object.keys(from)) {
|
|
118
|
+
into[key] = copyValue(from[key]);
|
|
119
|
+
}
|
|
120
|
+
return into;
|
|
121
|
+
};
|
|
@@ -86,9 +86,15 @@ export const reporterList = ({
|
|
|
86
86
|
Object.keys(testPlanResult.results).length === 0
|
|
87
87
|
) {
|
|
88
88
|
return {
|
|
89
|
-
afterEachInOrder: (execution) => {
|
|
90
|
-
const log = renderExecutionLog(
|
|
91
|
-
|
|
89
|
+
afterEachInOrder: (execution, testPlanResult, testPlanHelpers) => {
|
|
90
|
+
const log = renderExecutionLog(
|
|
91
|
+
execution,
|
|
92
|
+
logOptions,
|
|
93
|
+
testPlanHelpers,
|
|
94
|
+
);
|
|
95
|
+
if (log) {
|
|
96
|
+
write(log);
|
|
97
|
+
}
|
|
92
98
|
},
|
|
93
99
|
afterAll: async () => {
|
|
94
100
|
await write(renderOutro(testPlanResult, logOptions));
|
|
@@ -156,12 +162,18 @@ export const reporterList = ({
|
|
|
156
162
|
console.warn(warning.message);
|
|
157
163
|
});
|
|
158
164
|
},
|
|
159
|
-
afterEachInOrder: (execution) => {
|
|
165
|
+
afterEachInOrder: (execution, testPlanResult, testPlanHelpers) => {
|
|
160
166
|
oneExecutionWritten = true;
|
|
161
167
|
dynamicLog.clearDuringFunctionCall(
|
|
162
168
|
() => {
|
|
163
|
-
const log = renderExecutionLog(
|
|
164
|
-
|
|
169
|
+
const log = renderExecutionLog(
|
|
170
|
+
execution,
|
|
171
|
+
logOptions,
|
|
172
|
+
testPlanHelpers,
|
|
173
|
+
);
|
|
174
|
+
if (log) {
|
|
175
|
+
write(log);
|
|
176
|
+
}
|
|
165
177
|
},
|
|
166
178
|
// regenerate the dynamic log to put the leading "\n"
|
|
167
179
|
// because of oneExecutionWritten becoming true
|
|
@@ -218,11 +230,11 @@ const renderIntro = (testPlanResult, logOptions) => {
|
|
|
218
230
|
|
|
219
231
|
let title;
|
|
220
232
|
if (planified === 0) {
|
|
221
|
-
title = `
|
|
233
|
+
title = `nothing to execute`;
|
|
222
234
|
} else if (planified === 1) {
|
|
223
|
-
title = `1 execution
|
|
235
|
+
title = `1 execution ready`;
|
|
224
236
|
} else {
|
|
225
|
-
title = `${planified} executions
|
|
237
|
+
title = `${planified} executions ready`;
|
|
226
238
|
}
|
|
227
239
|
const lines = [];
|
|
228
240
|
lines.push(`directory: ${directory}`);
|
|
@@ -244,6 +256,11 @@ const renderIntro = (testPlanResult, logOptions) => {
|
|
|
244
256
|
testPlanLog += "}";
|
|
245
257
|
lines.push(`testPlan: ${testPlanLog}`);
|
|
246
258
|
}
|
|
259
|
+
if (testPlanResult.fragment) {
|
|
260
|
+
lines.push(
|
|
261
|
+
`fragment: ${testPlanResult.fragment}, executing only ${testPlanResult.fragmentStart}:${testPlanResult.fragmentEnd}`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
247
264
|
if (logOptions.platformInfo) {
|
|
248
265
|
os_line: {
|
|
249
266
|
let osLine = `os: `;
|
|
@@ -280,7 +297,36 @@ const renderIntro = (testPlanResult, logOptions) => {
|
|
|
280
297
|
})}\n`;
|
|
281
298
|
};
|
|
282
299
|
|
|
283
|
-
const renderExecutionLog = (
|
|
300
|
+
const renderExecutionLog = (
|
|
301
|
+
execution,
|
|
302
|
+
logOptions,
|
|
303
|
+
{ getPreviousExecution, getNextExecution },
|
|
304
|
+
) => {
|
|
305
|
+
if (execution.skipped) {
|
|
306
|
+
const skipReason = execution.skipReason;
|
|
307
|
+
const prev = getPreviousExecution(execution);
|
|
308
|
+
if (prev && prev.skipped && prev.skipReason === skipReason) {
|
|
309
|
+
return "";
|
|
310
|
+
}
|
|
311
|
+
let nextExecutionSkippedWithSameReason = 0;
|
|
312
|
+
let next = getNextExecution(execution);
|
|
313
|
+
while (next) {
|
|
314
|
+
if (!next.skipped || next.skipReason !== skipReason) {
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
nextExecutionSkippedWithSameReason++;
|
|
318
|
+
next = getNextExecution(next);
|
|
319
|
+
}
|
|
320
|
+
if (nextExecutionSkippedWithSameReason) {
|
|
321
|
+
const skippedGroupLog = descriptionFormatters.skippedGroup({
|
|
322
|
+
from: execution.index,
|
|
323
|
+
to: execution.index + nextExecutionSkippedWithSameReason,
|
|
324
|
+
skipReason,
|
|
325
|
+
});
|
|
326
|
+
return `${skippedGroupLog}\n`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
284
330
|
let log = "";
|
|
285
331
|
// label
|
|
286
332
|
{
|
|
@@ -361,15 +407,38 @@ const descriptionFormatters = {
|
|
|
361
407
|
executing: ({ fileRelativeUrl }) => {
|
|
362
408
|
return ANSI.color(`${fileRelativeUrl}`, COLOR_EXECUTING);
|
|
363
409
|
},
|
|
410
|
+
skippedGroup: ({ from, to, skipReason }) => {
|
|
411
|
+
let description = `${UNICODE.CIRCLE_DOTTED_RAW} ${from + 1}:${to + 1} skipped`;
|
|
412
|
+
if (skipReason) {
|
|
413
|
+
description += ` (${skipReason})`;
|
|
414
|
+
}
|
|
415
|
+
return ANSI.color(description, COLOR_SKIPPED);
|
|
416
|
+
},
|
|
417
|
+
skipped: ({ index, countersInOrder, fileRelativeUrl, skipReason }) => {
|
|
418
|
+
const total = countersInOrder.planified;
|
|
419
|
+
const number = fillLeft(index + 1, total, "0");
|
|
420
|
+
let description = `${UNICODE.CIRCLE_DOTTED_RAW} ${number}/${total} skipped ${fileRelativeUrl}`;
|
|
421
|
+
if (skipReason) {
|
|
422
|
+
description += ` (${skipReason})`;
|
|
423
|
+
}
|
|
424
|
+
return ANSI.color(description, COLOR_SKIPPED);
|
|
425
|
+
},
|
|
364
426
|
aborted: ({ index, countersInOrder, fileRelativeUrl }) => {
|
|
365
427
|
const total = countersInOrder.planified;
|
|
366
428
|
const number = fillLeft(index + 1, total, "0");
|
|
367
|
-
|
|
368
429
|
return ANSI.color(
|
|
369
430
|
`${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
|
|
370
431
|
COLOR_ABORTED,
|
|
371
432
|
);
|
|
372
433
|
},
|
|
434
|
+
cancelled: ({ index, countersInOrder, fileRelativeUrl }) => {
|
|
435
|
+
const total = countersInOrder.planified;
|
|
436
|
+
const number = fillLeft(index + 1, total, "0");
|
|
437
|
+
return ANSI.color(
|
|
438
|
+
`${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
|
|
439
|
+
COLOR_CANCELLED,
|
|
440
|
+
);
|
|
441
|
+
},
|
|
373
442
|
timedout: ({ index, countersInOrder, fileRelativeUrl, params }) => {
|
|
374
443
|
const total = countersInOrder.planified;
|
|
375
444
|
const number = fillLeft(index + 1, total, "0");
|
|
@@ -397,23 +466,15 @@ const descriptionFormatters = {
|
|
|
397
466
|
COLOR_COMPLETED,
|
|
398
467
|
);
|
|
399
468
|
},
|
|
400
|
-
cancelled: ({ index, countersInOrder, fileRelativeUrl }) => {
|
|
401
|
-
const total = countersInOrder.planified;
|
|
402
|
-
const number = fillLeft(index + 1, total, "0");
|
|
403
|
-
|
|
404
|
-
return ANSI.color(
|
|
405
|
-
`${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
|
|
406
|
-
COLOR_CANCELLED,
|
|
407
|
-
);
|
|
408
|
-
},
|
|
409
469
|
};
|
|
410
470
|
|
|
411
471
|
const COLOR_EXECUTING = ANSI.BLUE;
|
|
472
|
+
const COLOR_SKIPPED = ANSI.GREY;
|
|
412
473
|
const COLOR_ABORTED = ANSI.MAGENTA;
|
|
474
|
+
const COLOR_CANCELLED = ANSI.GREY;
|
|
413
475
|
const COLOR_TIMEOUT = ANSI.MAGENTA;
|
|
414
476
|
const COLOR_FAILED = ANSI.RED;
|
|
415
477
|
const COLOR_COMPLETED = ANSI.GREEN;
|
|
416
|
-
const COLOR_CANCELLED = ANSI.GREY;
|
|
417
478
|
const fillLeft = (value, biggestValue, char = " ") => {
|
|
418
479
|
const width = String(value).length;
|
|
419
480
|
const biggestWidth = String(biggestValue).length;
|
|
@@ -691,9 +752,15 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
|
|
|
691
752
|
if (counters.planified === 0) {
|
|
692
753
|
return ``;
|
|
693
754
|
}
|
|
755
|
+
if (counters.skipped === counters.planified) {
|
|
756
|
+
return `all ${ANSI.color(`skipped`, COLOR_SKIPPED)}`;
|
|
757
|
+
}
|
|
694
758
|
if (counters.aborted === counters.planified) {
|
|
695
759
|
return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
|
|
696
760
|
}
|
|
761
|
+
if (counters.cancelled === counters.planified) {
|
|
762
|
+
return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
|
|
763
|
+
}
|
|
697
764
|
if (counters.timedout === counters.planified) {
|
|
698
765
|
return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
|
|
699
766
|
}
|
|
@@ -703,9 +770,6 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
|
|
|
703
770
|
if (counters.completed === counters.planified) {
|
|
704
771
|
return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
|
|
705
772
|
}
|
|
706
|
-
if (counters.cancelled === counters.planified) {
|
|
707
|
-
return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
|
|
708
|
-
}
|
|
709
773
|
const parts = [];
|
|
710
774
|
if (counters.timedout) {
|
|
711
775
|
parts.push(
|
|
@@ -728,6 +792,9 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
|
|
|
728
792
|
`${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
|
|
729
793
|
);
|
|
730
794
|
}
|
|
795
|
+
if (counters.skipped) {
|
|
796
|
+
parts.push(`${counters.skipped} ${ANSI.color(`skipped`, COLOR_SKIPPED)}`);
|
|
797
|
+
}
|
|
731
798
|
if (showProgression) {
|
|
732
799
|
if (counters.executing) {
|
|
733
800
|
parts.push(`${counters.executing} executing`);
|
package/src/main.js
CHANGED
|
@@ -14,6 +14,7 @@ export { reportCoverageAsHtml } from "./coverage/report_coverage_as_html.js";
|
|
|
14
14
|
export { reporterList } from "./execution/reporters/reporter_list.js";
|
|
15
15
|
// other
|
|
16
16
|
export { reportAsJunitXml } from "./execution/junit_xml_file/report_as_junit_xml.js";
|
|
17
|
+
export { reportAsJson } from "./execution/report_as_json.js";
|
|
17
18
|
// advanced
|
|
18
19
|
export { execute } from "./execution/execute.js";
|
|
19
20
|
|
|
@@ -19,7 +19,7 @@ const createFirefoxRuntime = ({
|
|
|
19
19
|
if (disableOnWindowsBecauseFlaky === undefined) {
|
|
20
20
|
// https://github.com/microsoft/playwright/issues/1396
|
|
21
21
|
console.warn(
|
|
22
|
-
`Windows + firefox detected: executions on firefox will be ignored
|
|
22
|
+
`Windows + firefox detected: executions on firefox will be ignored (firefox is flaky on windows).
|
|
23
23
|
To disable this warning, use disableOnWindowsBecauseFlaky: true
|
|
24
24
|
To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
|
|
25
25
|
);
|
|
@@ -28,6 +28,7 @@ To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
|
|
|
28
28
|
if (disableOnWindowsBecauseFlaky) {
|
|
29
29
|
return {
|
|
30
30
|
disabled: true,
|
|
31
|
+
disabledReason: "flaky on windows",
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { fork } from "node:child_process";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import supportsColor from "supports-color";
|
|
4
3
|
import { Abort, raceCallbacks } from "@jsenv/abort";
|
|
5
|
-
import { createDetailedMessage } from "@jsenv/humanize";
|
|
4
|
+
import { UNICODE, ANSI, createDetailedMessage } from "@jsenv/humanize";
|
|
6
5
|
import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
|
|
7
6
|
|
|
8
7
|
import { createChildExecOptions } from "./child_exec_options.js";
|
|
@@ -37,11 +36,8 @@ export const nodeChildProcess = ({
|
|
|
37
36
|
env = {
|
|
38
37
|
...env,
|
|
39
38
|
JSENV: true,
|
|
40
|
-
FORCE_COLOR:
|
|
41
|
-
|
|
42
|
-
// GitHub workflow does support ANSI but "supports-color" returns false
|
|
43
|
-
// because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
|
|
44
|
-
process.env.GITHUB_WORKFLOW,
|
|
39
|
+
...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
|
|
40
|
+
...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
|
|
45
41
|
};
|
|
46
42
|
|
|
47
43
|
return {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// https://github.com/avajs/ava/blob/576f534b345259055c95fa0c2b33bef10847a2af/lib/worker/base.js
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import supportsColor from "supports-color";
|
|
7
6
|
import { Abort, raceCallbacks } from "@jsenv/abort";
|
|
7
|
+
import { UNICODE, ANSI } from "@jsenv/humanize";
|
|
8
8
|
import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
|
|
9
9
|
|
|
10
10
|
import { createChildExecOptions } from "./child_exec_options.js";
|
|
@@ -33,11 +33,8 @@ export const nodeWorkerThread = ({
|
|
|
33
33
|
env = {
|
|
34
34
|
...env,
|
|
35
35
|
JSENV: true,
|
|
36
|
-
FORCE_COLOR:
|
|
37
|
-
|
|
38
|
-
// GitHub workflow does support ANSI but "supports-color" returns false
|
|
39
|
-
// because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
|
|
40
|
-
process.env.GITHUB_WORKFLOW,
|
|
36
|
+
...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
|
|
37
|
+
...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
|
|
41
38
|
};
|
|
42
39
|
|
|
43
40
|
return {
|