@elench/testkit 0.1.34 → 0.1.36
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/README.md +30 -11
- package/lib/cli/args.mjs +28 -20
- package/lib/cli/args.test.mjs +22 -17
- package/lib/cli/index.mjs +27 -38
- package/lib/config/index.mjs +116 -0
- package/lib/reporters/playwright.mjs +41 -5
- package/lib/reporters/playwright.test.mjs +83 -0
- package/lib/runner/formatting.mjs +36 -4
- package/lib/runner/formatting.test.mjs +49 -0
- package/lib/runner/orchestrator.mjs +13 -16
- package/lib/runner/planning.mjs +65 -13
- package/lib/runner/planning.test.mjs +57 -5
- package/lib/runner/playwright-runner.mjs +2 -1
- package/lib/runner/reporting.mjs +43 -24
- package/lib/runner/reporting.test.mjs +41 -20
- package/lib/runner/results.mjs +56 -15
- package/lib/runner/results.test.mjs +114 -0
- package/lib/runner/selection.mjs +9 -7
- package/lib/runner/selection.test.mjs +5 -6
- package/lib/runner/suite-selection.mjs +91 -0
- package/lib/runner/suite-selection.test.mjs +42 -0
- package/lib/setup/index.d.ts +16 -0
- package/package.json +1 -1
|
@@ -7,12 +7,18 @@ export function formatDuration(durationMs) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function formatServiceSummary(result) {
|
|
10
|
-
const
|
|
10
|
+
const skippedSuites = result.skippedSuiteCount || 0;
|
|
11
|
+
const passedSuites = result.completedSuiteCount - result.failedSuiteCount - skippedSuites;
|
|
11
12
|
const notRunSuites = result.suiteCount - result.completedSuiteCount;
|
|
12
13
|
let detail = `${passedSuites}/${result.suiteCount} suites passed`;
|
|
13
14
|
if ((result.totalFileCount || 0) > 0) {
|
|
14
15
|
detail += `, ${result.passedFileCount}/${result.totalFileCount} files passed`;
|
|
15
16
|
}
|
|
17
|
+
if (skippedSuites > 0) {
|
|
18
|
+
detail += `, ${skippedSuites} ${pluralize(skippedSuites, "suite", "suites")} skipped`;
|
|
19
|
+
} else if ((result.skippedFileCount || 0) > 0) {
|
|
20
|
+
detail += `, ${result.skippedFileCount} ${pluralize(result.skippedFileCount, "file", "files")} skipped`;
|
|
21
|
+
}
|
|
16
22
|
if (notRunSuites > 0) {
|
|
17
23
|
detail += `, ${notRunSuites} ${pluralize(notRunSuites, "suite", "suites")} not run`;
|
|
18
24
|
} else if ((result.notRunFileCount || 0) > 0) {
|
|
@@ -66,10 +72,18 @@ export function buildRunSummaryLines(results, durationMs) {
|
|
|
66
72
|
(sum, result) => sum + result.completedSuiteCount,
|
|
67
73
|
0
|
|
68
74
|
);
|
|
75
|
+
const skippedSuites = executedServices.reduce(
|
|
76
|
+
(sum, result) => sum + (result.skippedSuiteCount || 0),
|
|
77
|
+
0
|
|
78
|
+
);
|
|
69
79
|
const failedSuites = executedServices.reduce((sum, result) => sum + result.failedSuiteCount, 0);
|
|
70
|
-
const passedSuites = completedSuites - failedSuites;
|
|
80
|
+
const passedSuites = completedSuites - failedSuites - skippedSuites;
|
|
71
81
|
const totalFiles = executedServices.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
|
|
72
82
|
const passedFiles = executedServices.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
|
|
83
|
+
const skippedFiles = executedServices.reduce(
|
|
84
|
+
(sum, result) => sum + (result.skippedFileCount || 0),
|
|
85
|
+
0
|
|
86
|
+
);
|
|
73
87
|
const lines = [
|
|
74
88
|
"",
|
|
75
89
|
"══ Summary ══",
|
|
@@ -77,6 +91,8 @@ export function buildRunSummaryLines(results, durationMs) {
|
|
|
77
91
|
`services ${passedServices.length}/${executedServices.length} passed`,
|
|
78
92
|
`suites ${passedSuites}/${totalSuites} passed`,
|
|
79
93
|
totalFiles > 0 ? `files ${passedFiles}/${totalFiles} passed` : null,
|
|
94
|
+
skippedSuites > 0 ? `suites ${skippedSuites} skipped` : null,
|
|
95
|
+
skippedFiles > 0 ? `files ${skippedFiles} skipped` : null,
|
|
80
96
|
skippedServices.length > 0 ? `${skippedServices.length} skipped` : null,
|
|
81
97
|
`duration ${formatDuration(durationMs)}`,
|
|
82
98
|
]
|
|
@@ -85,8 +101,14 @@ export function buildRunSummaryLines(results, durationMs) {
|
|
|
85
101
|
];
|
|
86
102
|
|
|
87
103
|
for (const result of results) {
|
|
88
|
-
const status = result
|
|
89
|
-
|
|
104
|
+
const status = isServiceEffectivelySkipped(result)
|
|
105
|
+
? "SKIP"
|
|
106
|
+
: result.failed
|
|
107
|
+
? "FAIL"
|
|
108
|
+
: "PASS";
|
|
109
|
+
const detail = result.skipped
|
|
110
|
+
? "no matching suites"
|
|
111
|
+
: formatServiceSummary(result);
|
|
90
112
|
lines.push(
|
|
91
113
|
`${status.padEnd(4)} ${result.name.padEnd(longestServiceName(results))} ${detail} · ${formatDuration(result.durationMs)}`
|
|
92
114
|
);
|
|
@@ -127,3 +149,13 @@ function sanitizeErrorMessage(message) {
|
|
|
127
149
|
function pluralize(value, singular, plural) {
|
|
128
150
|
return value === 1 ? singular : plural;
|
|
129
151
|
}
|
|
152
|
+
|
|
153
|
+
function isServiceEffectivelySkipped(result) {
|
|
154
|
+
if (result.skipped) return true;
|
|
155
|
+
return (
|
|
156
|
+
!result.failed &&
|
|
157
|
+
(result.skippedSuiteCount || 0) > 0 &&
|
|
158
|
+
(result.skippedSuiteCount || 0) === result.suiteCount &&
|
|
159
|
+
(result.notRunFileCount || 0) === 0
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -28,14 +28,31 @@ describe("runner formatting", () => {
|
|
|
28
28
|
formatServiceSummary({
|
|
29
29
|
completedSuiteCount: 2,
|
|
30
30
|
failedSuiteCount: 1,
|
|
31
|
+
skippedSuiteCount: 0,
|
|
31
32
|
suiteCount: 3,
|
|
32
33
|
totalFileCount: 5,
|
|
33
34
|
passedFileCount: 3,
|
|
35
|
+
skippedFileCount: 0,
|
|
34
36
|
notRunFileCount: 1,
|
|
35
37
|
})
|
|
36
38
|
).toBe("1/3 suites passed, 3/5 files passed, 1 suite not run");
|
|
37
39
|
});
|
|
38
40
|
|
|
41
|
+
it("formats skipped suites distinctly from passed suites", () => {
|
|
42
|
+
expect(
|
|
43
|
+
formatServiceSummary({
|
|
44
|
+
completedSuiteCount: 2,
|
|
45
|
+
failedSuiteCount: 0,
|
|
46
|
+
skippedSuiteCount: 1,
|
|
47
|
+
suiteCount: 2,
|
|
48
|
+
totalFileCount: 3,
|
|
49
|
+
passedFileCount: 1,
|
|
50
|
+
skippedFileCount: 2,
|
|
51
|
+
notRunFileCount: 0,
|
|
52
|
+
})
|
|
53
|
+
).toBe("1/2 suites passed, 1/3 files passed, 1 suite skipped");
|
|
54
|
+
});
|
|
55
|
+
|
|
39
56
|
it("formats batch descriptors", () => {
|
|
40
57
|
expect(formatBatchDescriptor({ framework: "k6", tasks: [{}, {}] })).toBe(" (2 files)");
|
|
41
58
|
expect(formatBatchDescriptor({ framework: "playwright", tasks: [{}] })).toBe(
|
|
@@ -71,9 +88,12 @@ describe("runner formatting", () => {
|
|
|
71
88
|
failed: true,
|
|
72
89
|
suiteCount: 2,
|
|
73
90
|
completedSuiteCount: 2,
|
|
91
|
+
skippedSuiteCount: 0,
|
|
74
92
|
failedSuiteCount: 1,
|
|
75
93
|
totalFileCount: 3,
|
|
76
94
|
passedFileCount: 2,
|
|
95
|
+
skippedFileCount: 0,
|
|
96
|
+
notRunFileCount: 0,
|
|
77
97
|
durationMs: 20_000,
|
|
78
98
|
suites: [
|
|
79
99
|
{
|
|
@@ -97,4 +117,33 @@ describe("runner formatting", () => {
|
|
|
97
117
|
expect(lines.join("\n")).toContain("worker error: worker broke");
|
|
98
118
|
expect(lines.at(-1)).toBe("Result: FAILED (1/1 services failed)");
|
|
99
119
|
});
|
|
120
|
+
|
|
121
|
+
it("marks services with only skipped suites as SKIP", () => {
|
|
122
|
+
const lines = buildRunSummaryLines(
|
|
123
|
+
[
|
|
124
|
+
{
|
|
125
|
+
name: "api",
|
|
126
|
+
skipped: false,
|
|
127
|
+
failed: false,
|
|
128
|
+
suiteCount: 1,
|
|
129
|
+
completedSuiteCount: 1,
|
|
130
|
+
skippedSuiteCount: 1,
|
|
131
|
+
failedSuiteCount: 0,
|
|
132
|
+
totalFileCount: 1,
|
|
133
|
+
passedFileCount: 0,
|
|
134
|
+
skippedFileCount: 1,
|
|
135
|
+
notRunFileCount: 0,
|
|
136
|
+
durationMs: 0,
|
|
137
|
+
suites: [],
|
|
138
|
+
errors: [],
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
0
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(lines.join("\n")).toContain("suites 1 skipped");
|
|
145
|
+
expect(lines.join("\n")).toContain("files 1 skipped");
|
|
146
|
+
expect(lines.join("\n")).toContain("SKIP api");
|
|
147
|
+
expect(lines.at(-1)).toBe("Result: PASSED");
|
|
148
|
+
});
|
|
100
149
|
});
|
|
@@ -33,7 +33,7 @@ import { createWorker, runWorker } from "./worker-loop.mjs";
|
|
|
33
33
|
import { findUnmatchedRequestedFiles, isFullRunSelection } from "./selection.mjs";
|
|
34
34
|
import { uploadTelemetryArtifact } from "../telemetry/index.mjs";
|
|
35
35
|
|
|
36
|
-
export async function runAll(configs,
|
|
36
|
+
export async function runAll(configs, typeValues, suiteSelectors, opts, allConfigs = configs) {
|
|
37
37
|
const configMap = new Map(allConfigs.map((config) => [config.name, config]));
|
|
38
38
|
const startedAt = Date.now();
|
|
39
39
|
const telemetry = configs[0]?.telemetry || null;
|
|
@@ -51,9 +51,8 @@ export async function runAll(configs, suiteType, suiteNames, opts, allConfigs =
|
|
|
51
51
|
if (requestedFiles.length > 0) {
|
|
52
52
|
const unmatchedFiles = findUnmatchedRequestedFiles(
|
|
53
53
|
configs,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
opts.framework || "all",
|
|
54
|
+
typeValues,
|
|
55
|
+
suiteSelectors,
|
|
57
56
|
requestedFiles,
|
|
58
57
|
collectSuites,
|
|
59
58
|
normalizePathSeparators
|
|
@@ -69,9 +68,9 @@ export async function runAll(configs, suiteType, suiteNames, opts, allConfigs =
|
|
|
69
68
|
opts.writeStatus &&
|
|
70
69
|
!opts.allowPartialStatus &&
|
|
71
70
|
!isFullRunSelection(
|
|
72
|
-
|
|
71
|
+
typeValues,
|
|
72
|
+
suiteSelectors,
|
|
73
73
|
requestedFiles,
|
|
74
|
-
opts.framework || "all",
|
|
75
74
|
opts.shard || null,
|
|
76
75
|
opts.serviceFilter || null
|
|
77
76
|
)
|
|
@@ -82,7 +81,7 @@ export async function runAll(configs, suiteType, suiteNames, opts, allConfigs =
|
|
|
82
81
|
);
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
const servicePlans = collectServicePlans(configs, configMap,
|
|
84
|
+
const servicePlans = collectServicePlans(configs, configMap, typeValues, suiteSelectors, opts);
|
|
86
85
|
const trackers = buildServiceTrackers(servicePlans, startedAt);
|
|
87
86
|
const executedPlans = servicePlans.filter((plan) => !plan.skipped);
|
|
88
87
|
let workerCount = 0;
|
|
@@ -144,10 +143,9 @@ export async function runAll(configs, suiteType, suiteNames, opts, allConfigs =
|
|
|
144
143
|
finishedAt,
|
|
145
144
|
requestedJobs: opts.jobs || 1,
|
|
146
145
|
workerCount,
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
typeValues,
|
|
147
|
+
suiteSelectors,
|
|
149
148
|
fileNames: requestedFiles,
|
|
150
|
-
framework: opts.framework || "all",
|
|
151
149
|
shard: opts.shard || null,
|
|
152
150
|
serviceFilter: opts.serviceFilter || null,
|
|
153
151
|
metadata,
|
|
@@ -161,10 +159,9 @@ export async function runAll(configs, suiteType, suiteNames, opts, allConfigs =
|
|
|
161
159
|
buildStatusArtifact({
|
|
162
160
|
productDir,
|
|
163
161
|
results,
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
typeValues,
|
|
163
|
+
suiteSelectors,
|
|
166
164
|
fileNames: requestedFiles,
|
|
167
|
-
framework: opts.framework || "all",
|
|
168
165
|
shard: opts.shard || null,
|
|
169
166
|
serviceFilter: opts.serviceFilter || null,
|
|
170
167
|
metadata,
|
|
@@ -188,17 +185,17 @@ export async function runAll(configs, suiteType, suiteNames, opts, allConfigs =
|
|
|
188
185
|
}
|
|
189
186
|
}
|
|
190
187
|
|
|
191
|
-
function collectServicePlans(configs, configMap,
|
|
188
|
+
function collectServicePlans(configs, configMap, typeValues, suiteSelectors, opts) {
|
|
192
189
|
return configs.map((config) => {
|
|
193
190
|
console.log(`\n══ ${config.name} ══`);
|
|
194
191
|
const suites = applyShard(
|
|
195
|
-
collectSuites(config,
|
|
192
|
+
collectSuites(config, typeValues, suiteSelectors, opts.fileNames || [], opts),
|
|
196
193
|
opts.shard
|
|
197
194
|
);
|
|
198
195
|
|
|
199
196
|
if (suites.length === 0) {
|
|
200
197
|
console.log(
|
|
201
|
-
`No test files for ${config.name}
|
|
198
|
+
`No test files for ${config.name} types=${typeValues.join(",") || "all"} suites=${suiteSelectors.map((selector) => selector.raw).join(",") || "all"} files=${(opts.fileNames || []).join(",") || "all"} — skipping`
|
|
202
199
|
);
|
|
203
200
|
return {
|
|
204
201
|
config,
|
package/lib/runner/planning.mjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { buildTimingKey, estimateTaskDuration } from "../timing/index.mjs";
|
|
2
|
+
import {
|
|
3
|
+
matchesSelectedTypes,
|
|
4
|
+
matchesSuiteSelectors,
|
|
5
|
+
suiteSelectionType,
|
|
6
|
+
} from "./suite-selection.mjs";
|
|
2
7
|
|
|
3
8
|
const TYPE_ORDER = ["dal", "integration", "e2e", "load"];
|
|
4
9
|
|
|
@@ -34,44 +39,50 @@ export function resolveRuntimeConfigs(targetConfig, configMap) {
|
|
|
34
39
|
return ordered;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
export function collectSuites(config,
|
|
38
|
-
const types =
|
|
39
|
-
suiteType === "all"
|
|
40
|
-
? orderedTypes(Object.keys(config.suites))
|
|
41
|
-
: [suiteType === "int" ? "integration" : suiteType];
|
|
42
|
-
|
|
43
|
-
const selectedNames = new Set(suiteNames);
|
|
42
|
+
export function collectSuites(config, typeValues, suiteSelectors, fileNames = [], opts = {}) {
|
|
44
43
|
const selectedFiles = new Set(fileNames.map(normalizePathSeparators));
|
|
45
44
|
const suites = [];
|
|
46
45
|
let orderIndex = 0;
|
|
47
46
|
|
|
48
|
-
for (const type of
|
|
47
|
+
for (const type of orderedTypes(Object.keys(config.suites))) {
|
|
49
48
|
for (const suite of config.suites[type] || []) {
|
|
50
49
|
const framework = suite.framework || "k6";
|
|
51
|
-
const
|
|
50
|
+
const displayType = suiteSelectionType(type, framework);
|
|
51
|
+
const selectedSuiteFiles =
|
|
52
52
|
selectedFiles.size === 0
|
|
53
53
|
? suite.files
|
|
54
54
|
: suite.files.filter((file) => selectedFiles.has(normalizePathSeparators(file)));
|
|
55
|
-
if (
|
|
56
|
-
if (
|
|
57
|
-
if (
|
|
55
|
+
if (!matchesSelectedTypes(displayType, typeValues)) continue;
|
|
56
|
+
if (!matchesSuiteSelectors(displayType, suite.name, suiteSelectors)) continue;
|
|
57
|
+
if (selectedSuiteFiles.length === 0) continue;
|
|
58
|
+
|
|
59
|
+
const { files, skippedFiles } = applySkipRules(
|
|
60
|
+
config,
|
|
61
|
+
displayType,
|
|
62
|
+
suite.name,
|
|
63
|
+
selectedSuiteFiles,
|
|
64
|
+
opts
|
|
65
|
+
);
|
|
58
66
|
|
|
59
67
|
suites.push({
|
|
60
68
|
...suite,
|
|
61
69
|
files,
|
|
70
|
+
skippedFiles,
|
|
62
71
|
framework,
|
|
63
72
|
type,
|
|
73
|
+
displayType,
|
|
64
74
|
orderIndex,
|
|
65
75
|
sortKey: `${type}:${suite.name}`,
|
|
66
76
|
weight:
|
|
67
77
|
suite.testkit?.weight ||
|
|
68
78
|
(framework === "playwright"
|
|
69
|
-
? Math.max(2, files.length)
|
|
79
|
+
? Math.max(2, Math.max(1, files.length))
|
|
70
80
|
: Math.max(1, files.length)),
|
|
71
81
|
maxFileConcurrency:
|
|
72
82
|
framework === "k6" || framework === "playwright"
|
|
73
83
|
? suite.testkit?.maxFileConcurrency || 1
|
|
74
84
|
: 1,
|
|
85
|
+
totalFileCount: selectedSuiteFiles.length,
|
|
75
86
|
});
|
|
76
87
|
orderIndex += 1;
|
|
77
88
|
}
|
|
@@ -261,6 +272,47 @@ function normalizePathSeparators(filePath) {
|
|
|
261
272
|
return String(filePath).split("\\").join("/");
|
|
262
273
|
}
|
|
263
274
|
|
|
275
|
+
function applySkipRules(config, displayType, suiteName, files, opts = {}) {
|
|
276
|
+
if (opts.ignoreSkipRules) {
|
|
277
|
+
return {
|
|
278
|
+
files,
|
|
279
|
+
skippedFiles: [],
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const skip = config.testkit?.skip;
|
|
283
|
+
if (!skip) {
|
|
284
|
+
return {
|
|
285
|
+
files,
|
|
286
|
+
skippedFiles: [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const matchingSuiteRules = skip.suites.filter((rule) =>
|
|
291
|
+
matchesSuiteSelectors(displayType, suiteName, [rule.selector])
|
|
292
|
+
);
|
|
293
|
+
const suiteReason = matchingSuiteRules[0]?.reason || null;
|
|
294
|
+
const runnableFiles = [];
|
|
295
|
+
const skippedFiles = [];
|
|
296
|
+
|
|
297
|
+
for (const file of files) {
|
|
298
|
+
const normalizedFile = normalizePathSeparators(file);
|
|
299
|
+
const reason = skip.fileReasonByPath.get(normalizedFile) || suiteReason;
|
|
300
|
+
if (reason) {
|
|
301
|
+
skippedFiles.push({
|
|
302
|
+
path: normalizedFile,
|
|
303
|
+
reason,
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
runnableFiles.push(file);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
files: runnableFiles,
|
|
312
|
+
skippedFiles,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
264
316
|
export function buildGraphDirName(runtimeNames) {
|
|
265
317
|
const slug = runtimeNames.map(slugSegment).join("__");
|
|
266
318
|
return slug.length > 0 ? slug : "graph";
|
|
@@ -38,7 +38,7 @@ describe("runner-planning", () => {
|
|
|
38
38
|
expect(() => resolveRuntimeConfigs(frontend, configMap)).toThrow("Dependency cycle");
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it("collects suites with
|
|
41
|
+
it("collects suites with user-facing type selection", () => {
|
|
42
42
|
const config = makeConfig("api", {
|
|
43
43
|
suites: {
|
|
44
44
|
integration: [
|
|
@@ -57,15 +57,17 @@ describe("runner-planning", () => {
|
|
|
57
57
|
},
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
expect(collectSuites(config, "int", [],
|
|
60
|
+
expect(collectSuites(config, ["int"], [], [])[0]).toMatchObject({
|
|
61
61
|
name: "health",
|
|
62
62
|
type: "integration",
|
|
63
|
+
displayType: "int",
|
|
63
64
|
framework: "k6",
|
|
64
65
|
weight: 1,
|
|
65
66
|
});
|
|
66
67
|
|
|
67
|
-
expect(collectSuites(config, "
|
|
68
|
+
expect(collectSuites(config, ["pw"], [], [])[0]).toMatchObject({
|
|
68
69
|
name: "auth",
|
|
70
|
+
displayType: "pw",
|
|
69
71
|
framework: "playwright",
|
|
70
72
|
weight: 2,
|
|
71
73
|
});
|
|
@@ -73,9 +75,8 @@ describe("runner-planning", () => {
|
|
|
73
75
|
expect(
|
|
74
76
|
collectSuites(
|
|
75
77
|
config,
|
|
76
|
-
"all",
|
|
78
|
+
["all"],
|
|
77
79
|
[],
|
|
78
|
-
"all",
|
|
79
80
|
["tests/frontend/e2e/signup.pw.testkit.ts"]
|
|
80
81
|
)[0]
|
|
81
82
|
).toMatchObject({
|
|
@@ -84,6 +85,57 @@ describe("runner-planning", () => {
|
|
|
84
85
|
});
|
|
85
86
|
});
|
|
86
87
|
|
|
88
|
+
it("keeps skipped files visible while removing them from runnable work", () => {
|
|
89
|
+
const config = makeConfig("api", {
|
|
90
|
+
suites: {
|
|
91
|
+
integration: [
|
|
92
|
+
{
|
|
93
|
+
name: "billing",
|
|
94
|
+
files: [
|
|
95
|
+
"__testkit__/billing/a.int.testkit.ts",
|
|
96
|
+
"__testkit__/billing/b.int.testkit.ts",
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
testkit: {
|
|
102
|
+
dependsOn: [],
|
|
103
|
+
skip: {
|
|
104
|
+
fileReasonByPath: new Map([
|
|
105
|
+
["__testkit__/billing/a.int.testkit.ts", "Billing is stubbed"],
|
|
106
|
+
]),
|
|
107
|
+
suites: [],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(collectSuites(config, ["int"], [], [])).toEqual([
|
|
113
|
+
expect.objectContaining({
|
|
114
|
+
name: "billing",
|
|
115
|
+
files: ["__testkit__/billing/b.int.testkit.ts"],
|
|
116
|
+
skippedFiles: [
|
|
117
|
+
{
|
|
118
|
+
path: "__testkit__/billing/a.int.testkit.ts",
|
|
119
|
+
reason: "Billing is stubbed",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
totalFileCount: 2,
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
expect(collectSuites(config, ["int"], [], [], { ignoreSkipRules: true })).toEqual([
|
|
127
|
+
expect.objectContaining({
|
|
128
|
+
name: "billing",
|
|
129
|
+
files: [
|
|
130
|
+
"__testkit__/billing/a.int.testkit.ts",
|
|
131
|
+
"__testkit__/billing/b.int.testkit.ts",
|
|
132
|
+
],
|
|
133
|
+
skippedFiles: [],
|
|
134
|
+
totalFileCount: 2,
|
|
135
|
+
}),
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
|
|
87
139
|
it("applies shards, builds graphs, queues tasks, and claims batches", () => {
|
|
88
140
|
const api = makeConfig("api");
|
|
89
141
|
const frontend = makeConfig("frontend");
|
|
@@ -62,7 +62,8 @@ export async function runPlaywrightBatch(targetConfig, batch, lifecycle) {
|
|
|
62
62
|
if (fileResult) {
|
|
63
63
|
return {
|
|
64
64
|
task,
|
|
65
|
-
failed: fileResult.failed,
|
|
65
|
+
failed: fileResult.status === "failed",
|
|
66
|
+
status: fileResult.status,
|
|
66
67
|
error: fileResult.error,
|
|
67
68
|
durationMs:
|
|
68
69
|
fileResult.durationMs > 0
|
package/lib/runner/reporting.mjs
CHANGED
|
@@ -3,26 +3,30 @@ import path from "path";
|
|
|
3
3
|
export function buildStatusArtifact({
|
|
4
4
|
productDir,
|
|
5
5
|
results,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
typeValues,
|
|
7
|
+
suiteSelectors,
|
|
8
8
|
fileNames,
|
|
9
|
-
framework,
|
|
10
9
|
shard,
|
|
11
10
|
serviceFilter,
|
|
12
11
|
metadata,
|
|
13
12
|
}) {
|
|
14
13
|
const executedResults = results.filter((result) => !result.skipped);
|
|
14
|
+
const effectivelySkippedResults = executedResults.filter(isEffectivelySkippedService);
|
|
15
15
|
const tests = [];
|
|
16
16
|
|
|
17
17
|
for (const result of executedResults) {
|
|
18
18
|
for (const suite of result.suites) {
|
|
19
19
|
for (const file of suite.files) {
|
|
20
|
-
|
|
20
|
+
const test = {
|
|
21
21
|
service: result.name,
|
|
22
22
|
type: suite.type,
|
|
23
23
|
path: file.path,
|
|
24
24
|
status: file.status,
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
|
+
if (file.reason) {
|
|
27
|
+
test.reason = file.reason;
|
|
28
|
+
}
|
|
29
|
+
tests.push(test);
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -37,34 +41,38 @@ export function buildStatusArtifact({
|
|
|
37
41
|
const summary = {
|
|
38
42
|
services: {
|
|
39
43
|
total: executedResults.length,
|
|
40
|
-
passed: executedResults.filter(
|
|
44
|
+
passed: executedResults.filter(
|
|
45
|
+
(result) => !result.failed && !isEffectivelySkippedService(result)
|
|
46
|
+
).length,
|
|
41
47
|
failed: executedResults.filter((result) => result.failed).length,
|
|
48
|
+
skipped: effectivelySkippedResults.length,
|
|
42
49
|
},
|
|
43
50
|
tests: {
|
|
44
51
|
total: tests.length,
|
|
45
52
|
passed: tests.filter((test) => test.status === "passed").length,
|
|
46
53
|
failed: tests.filter((test) => test.status === "failed").length,
|
|
54
|
+
skipped: tests.filter((test) => test.status === "skipped").length,
|
|
47
55
|
notRun: tests.filter((test) => test.status === "not_run").length,
|
|
48
56
|
},
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
const scope = {
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
types: [...(typeValues || ["all"])].sort(),
|
|
61
|
+
suiteSelectors: [...(suiteSelectors || [])].map((selector) => selector.raw).sort(),
|
|
54
62
|
fileNames: [...(fileNames || [])].sort(),
|
|
55
|
-
framework: formatFrameworkForArtifact(framework || "all"),
|
|
56
63
|
shard: shard || null,
|
|
57
64
|
serviceFilter: serviceFilter || null,
|
|
58
65
|
};
|
|
59
66
|
scope.isFullRun =
|
|
60
|
-
scope.
|
|
67
|
+
scope.types.length === 1 &&
|
|
68
|
+
scope.types[0] === "all" &&
|
|
69
|
+
scope.suiteSelectors.length === 0 &&
|
|
61
70
|
scope.fileNames.length === 0 &&
|
|
62
|
-
scope.framework === "all" &&
|
|
63
71
|
scope.shard === null &&
|
|
64
72
|
scope.serviceFilter === null;
|
|
65
73
|
|
|
66
74
|
return {
|
|
67
|
-
schemaVersion:
|
|
75
|
+
schemaVersion: 3,
|
|
68
76
|
source: "testkit",
|
|
69
77
|
notice: "Generated file. Do not edit manually.",
|
|
70
78
|
product: {
|
|
@@ -88,28 +96,30 @@ export function buildRunArtifact({
|
|
|
88
96
|
finishedAt,
|
|
89
97
|
requestedJobs,
|
|
90
98
|
workerCount,
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
typeValues,
|
|
100
|
+
suiteSelectors,
|
|
93
101
|
fileNames,
|
|
94
|
-
framework,
|
|
95
102
|
shard,
|
|
96
103
|
serviceFilter,
|
|
97
104
|
metadata,
|
|
98
105
|
summarizeDbBackend,
|
|
99
106
|
}) {
|
|
100
107
|
const executed = results.filter((result) => !result.skipped);
|
|
108
|
+
const effectivelySkippedServices = executed.filter(isEffectivelySkippedService);
|
|
101
109
|
const failedServices = executed.filter((result) => result.failed);
|
|
102
110
|
const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
|
|
103
111
|
const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
|
|
112
|
+
const skippedSuites = executed.reduce((sum, result) => sum + (result.skippedSuiteCount || 0), 0);
|
|
104
113
|
const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
|
|
105
114
|
const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
|
|
106
115
|
const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
|
|
107
116
|
const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
|
|
117
|
+
const skippedFiles = executed.reduce((sum, result) => sum + (result.skippedFileCount || 0), 0);
|
|
108
118
|
const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
|
|
109
119
|
const dbBackend = summarizeDbBackend(results);
|
|
110
120
|
|
|
111
121
|
return {
|
|
112
|
-
schemaVersion:
|
|
122
|
+
schemaVersion: 3,
|
|
113
123
|
source: "testkit",
|
|
114
124
|
generatedAt: new Date(finishedAt).toISOString(),
|
|
115
125
|
product: {
|
|
@@ -126,10 +136,9 @@ export function buildRunArtifact({
|
|
|
126
136
|
requestedJobs,
|
|
127
137
|
workerCount,
|
|
128
138
|
dbBackend,
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
types: typeValues,
|
|
140
|
+
suiteSelectors: suiteSelectors.map((selector) => selector.raw),
|
|
131
141
|
fileNames,
|
|
132
|
-
framework: formatFrameworkForArtifact(framework),
|
|
133
142
|
shard,
|
|
134
143
|
serviceFilter,
|
|
135
144
|
testkitVersion: metadata.testkitVersion,
|
|
@@ -137,19 +146,22 @@ export function buildRunArtifact({
|
|
|
137
146
|
summary: {
|
|
138
147
|
services: {
|
|
139
148
|
total: executed.length,
|
|
140
|
-
passed: executed.length - failedServices.length,
|
|
149
|
+
passed: executed.length - failedServices.length - effectivelySkippedServices.length,
|
|
141
150
|
failed: failedServices.length,
|
|
151
|
+
skipped: effectivelySkippedServices.length,
|
|
142
152
|
},
|
|
143
153
|
suites: {
|
|
144
154
|
total: totalSuites,
|
|
145
155
|
completed: completedSuites,
|
|
146
|
-
passed: completedSuites - failedSuites,
|
|
156
|
+
passed: completedSuites - failedSuites - skippedSuites,
|
|
147
157
|
failed: failedSuites,
|
|
158
|
+
skipped: skippedSuites,
|
|
148
159
|
},
|
|
149
160
|
files: {
|
|
150
161
|
total: totalFiles,
|
|
151
162
|
passed: passedFiles,
|
|
152
163
|
failed: failedFiles,
|
|
164
|
+
skipped: skippedFiles,
|
|
153
165
|
notRun: notRunFiles,
|
|
154
166
|
},
|
|
155
167
|
},
|
|
@@ -159,11 +171,13 @@ export function buildRunArtifact({
|
|
|
159
171
|
skipped: result.skipped,
|
|
160
172
|
suiteCount: result.suiteCount,
|
|
161
173
|
completedSuiteCount: result.completedSuiteCount,
|
|
174
|
+
skippedSuiteCount: result.skippedSuiteCount,
|
|
162
175
|
failedSuiteCount: result.failedSuiteCount,
|
|
163
176
|
totalFileCount: result.totalFileCount,
|
|
164
177
|
completedFileCount: result.completedFileCount,
|
|
165
178
|
passedFileCount: result.passedFileCount,
|
|
166
179
|
failedFileCount: result.failedFileCount,
|
|
180
|
+
skippedFileCount: result.skippedFileCount,
|
|
167
181
|
notRunFileCount: result.notRunFileCount,
|
|
168
182
|
durationMs: result.durationMs,
|
|
169
183
|
totalTaskDurationMs: result.totalTaskDurationMs,
|
|
@@ -174,7 +188,12 @@ export function buildRunArtifact({
|
|
|
174
188
|
};
|
|
175
189
|
}
|
|
176
190
|
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
191
|
+
function isEffectivelySkippedService(result) {
|
|
192
|
+
return (
|
|
193
|
+
!result.skipped &&
|
|
194
|
+
!result.failed &&
|
|
195
|
+
(result.skippedSuiteCount || 0) > 0 &&
|
|
196
|
+
(result.skippedSuiteCount || 0) === result.suiteCount &&
|
|
197
|
+
(result.notRunFileCount || 0) === 0
|
|
198
|
+
);
|
|
180
199
|
}
|