@elench/testkit 0.1.35 → 0.1.37
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 +20 -0
- package/lib/cli/index.mjs +4 -0
- 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/artifacts.mjs +72 -0
- package/lib/runner/default-runtime-runner.mjs +51 -2
- package/lib/runner/formatting.mjs +36 -4
- package/lib/runner/formatting.test.mjs +49 -0
- package/lib/runner/orchestrator.mjs +9 -2
- package/lib/runner/planning.mjs +55 -4
- package/lib/runner/planning.test.mjs +51 -0
- package/lib/runner/playwright-runner.mjs +2 -1
- package/lib/runner/reporting.mjs +34 -7
- package/lib/runner/reporting.test.mjs +30 -5
- package/lib/runner/results.mjs +61 -14
- package/lib/runner/results.test.mjs +114 -0
- package/lib/runner/selection.mjs +3 -1
- package/lib/runtime/index.d.ts +11 -0
- package/lib/runtime/index.mjs +3 -0
- package/lib/runtime-src/k6/artifacts.js +36 -0
- package/lib/setup/index.d.ts +16 -0
- package/package.json +1 -1
|
@@ -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
|
});
|
|
@@ -16,7 +16,13 @@ import {
|
|
|
16
16
|
} from "./results.mjs";
|
|
17
17
|
import { buildRunArtifact, buildStatusArtifact } from "./reporting.mjs";
|
|
18
18
|
import { buildRunSummaryLines, formatError } from "./formatting.mjs";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
loadTimings,
|
|
21
|
+
resetResultArtifacts,
|
|
22
|
+
saveTimings,
|
|
23
|
+
writeRunArtifact,
|
|
24
|
+
writeStatusArtifact,
|
|
25
|
+
} from "./artifacts.mjs";
|
|
20
26
|
import {
|
|
21
27
|
cleanupRunById,
|
|
22
28
|
cleanupRuns,
|
|
@@ -39,6 +45,7 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
|
|
|
39
45
|
const telemetry = configs[0]?.telemetry || null;
|
|
40
46
|
const productDir = configs[0]?.productDir || process.cwd();
|
|
41
47
|
await cleanupStaleRuns(productDir);
|
|
48
|
+
resetResultArtifacts(productDir);
|
|
42
49
|
const metadata = {
|
|
43
50
|
git: collectGitMetadata(productDir),
|
|
44
51
|
host: {
|
|
@@ -189,7 +196,7 @@ function collectServicePlans(configs, configMap, typeValues, suiteSelectors, opt
|
|
|
189
196
|
return configs.map((config) => {
|
|
190
197
|
console.log(`\n══ ${config.name} ══`);
|
|
191
198
|
const suites = applyShard(
|
|
192
|
-
collectSuites(config, typeValues, suiteSelectors, opts.fileNames || []),
|
|
199
|
+
collectSuites(config, typeValues, suiteSelectors, opts.fileNames || [], opts),
|
|
193
200
|
opts.shard
|
|
194
201
|
);
|
|
195
202
|
|
package/lib/runner/planning.mjs
CHANGED
|
@@ -39,7 +39,7 @@ export function resolveRuntimeConfigs(targetConfig, configMap) {
|
|
|
39
39
|
return ordered;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export function collectSuites(config, typeValues, suiteSelectors, fileNames = []) {
|
|
42
|
+
export function collectSuites(config, typeValues, suiteSelectors, fileNames = [], opts = {}) {
|
|
43
43
|
const selectedFiles = new Set(fileNames.map(normalizePathSeparators));
|
|
44
44
|
const suites = [];
|
|
45
45
|
let orderIndex = 0;
|
|
@@ -48,17 +48,26 @@ export function collectSuites(config, typeValues, suiteSelectors, fileNames = []
|
|
|
48
48
|
for (const suite of config.suites[type] || []) {
|
|
49
49
|
const framework = suite.framework || "k6";
|
|
50
50
|
const displayType = suiteSelectionType(type, framework);
|
|
51
|
-
const
|
|
51
|
+
const selectedSuiteFiles =
|
|
52
52
|
selectedFiles.size === 0
|
|
53
53
|
? suite.files
|
|
54
54
|
: suite.files.filter((file) => selectedFiles.has(normalizePathSeparators(file)));
|
|
55
55
|
if (!matchesSelectedTypes(displayType, typeValues)) continue;
|
|
56
56
|
if (!matchesSuiteSelectors(displayType, suite.name, suiteSelectors)) continue;
|
|
57
|
-
if (
|
|
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,
|
|
64
73
|
displayType,
|
|
@@ -67,12 +76,13 @@ export function collectSuites(config, typeValues, suiteSelectors, fileNames = []
|
|
|
67
76
|
weight:
|
|
68
77
|
suite.testkit?.weight ||
|
|
69
78
|
(framework === "playwright"
|
|
70
|
-
? Math.max(2, files.length)
|
|
79
|
+
? Math.max(2, Math.max(1, files.length))
|
|
71
80
|
: Math.max(1, files.length)),
|
|
72
81
|
maxFileConcurrency:
|
|
73
82
|
framework === "k6" || framework === "playwright"
|
|
74
83
|
? suite.testkit?.maxFileConcurrency || 1
|
|
75
84
|
: 1,
|
|
85
|
+
totalFileCount: selectedSuiteFiles.length,
|
|
76
86
|
});
|
|
77
87
|
orderIndex += 1;
|
|
78
88
|
}
|
|
@@ -262,6 +272,47 @@ function normalizePathSeparators(filePath) {
|
|
|
262
272
|
return String(filePath).split("\\").join("/");
|
|
263
273
|
}
|
|
264
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
|
+
|
|
265
316
|
export function buildGraphDirName(runtimeNames) {
|
|
266
317
|
const slug = runtimeNames.map(slugSegment).join("__");
|
|
267
318
|
return slug.length > 0 ? slug : "graph";
|
|
@@ -85,6 +85,57 @@ describe("runner-planning", () => {
|
|
|
85
85
|
});
|
|
86
86
|
});
|
|
87
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
|
+
|
|
88
139
|
it("applies shards, builds graphs, queues tasks, and claims batches", () => {
|
|
89
140
|
const api = makeConfig("api");
|
|
90
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
|
@@ -11,17 +11,22 @@ export function buildStatusArtifact({
|
|
|
11
11
|
metadata,
|
|
12
12
|
}) {
|
|
13
13
|
const executedResults = results.filter((result) => !result.skipped);
|
|
14
|
+
const effectivelySkippedResults = executedResults.filter(isEffectivelySkippedService);
|
|
14
15
|
const tests = [];
|
|
15
16
|
|
|
16
17
|
for (const result of executedResults) {
|
|
17
18
|
for (const suite of result.suites) {
|
|
18
19
|
for (const file of suite.files) {
|
|
19
|
-
|
|
20
|
+
const test = {
|
|
20
21
|
service: result.name,
|
|
21
22
|
type: suite.type,
|
|
22
23
|
path: file.path,
|
|
23
24
|
status: file.status,
|
|
24
|
-
}
|
|
25
|
+
};
|
|
26
|
+
if (file.reason) {
|
|
27
|
+
test.reason = file.reason;
|
|
28
|
+
}
|
|
29
|
+
tests.push(test);
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
}
|
|
@@ -36,13 +41,17 @@ export function buildStatusArtifact({
|
|
|
36
41
|
const summary = {
|
|
37
42
|
services: {
|
|
38
43
|
total: executedResults.length,
|
|
39
|
-
passed: executedResults.filter(
|
|
44
|
+
passed: executedResults.filter(
|
|
45
|
+
(result) => !result.failed && !isEffectivelySkippedService(result)
|
|
46
|
+
).length,
|
|
40
47
|
failed: executedResults.filter((result) => result.failed).length,
|
|
48
|
+
skipped: effectivelySkippedResults.length,
|
|
41
49
|
},
|
|
42
50
|
tests: {
|
|
43
51
|
total: tests.length,
|
|
44
52
|
passed: tests.filter((test) => test.status === "passed").length,
|
|
45
53
|
failed: tests.filter((test) => test.status === "failed").length,
|
|
54
|
+
skipped: tests.filter((test) => test.status === "skipped").length,
|
|
46
55
|
notRun: tests.filter((test) => test.status === "not_run").length,
|
|
47
56
|
},
|
|
48
57
|
};
|
|
@@ -63,7 +72,7 @@ export function buildStatusArtifact({
|
|
|
63
72
|
scope.serviceFilter === null;
|
|
64
73
|
|
|
65
74
|
return {
|
|
66
|
-
schemaVersion:
|
|
75
|
+
schemaVersion: 3,
|
|
67
76
|
source: "testkit",
|
|
68
77
|
notice: "Generated file. Do not edit manually.",
|
|
69
78
|
product: {
|
|
@@ -96,18 +105,21 @@ export function buildRunArtifact({
|
|
|
96
105
|
summarizeDbBackend,
|
|
97
106
|
}) {
|
|
98
107
|
const executed = results.filter((result) => !result.skipped);
|
|
108
|
+
const effectivelySkippedServices = executed.filter(isEffectivelySkippedService);
|
|
99
109
|
const failedServices = executed.filter((result) => result.failed);
|
|
100
110
|
const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
|
|
101
111
|
const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
|
|
112
|
+
const skippedSuites = executed.reduce((sum, result) => sum + (result.skippedSuiteCount || 0), 0);
|
|
102
113
|
const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
|
|
103
114
|
const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
|
|
104
115
|
const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
|
|
105
116
|
const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
|
|
117
|
+
const skippedFiles = executed.reduce((sum, result) => sum + (result.skippedFileCount || 0), 0);
|
|
106
118
|
const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
|
|
107
119
|
const dbBackend = summarizeDbBackend(results);
|
|
108
120
|
|
|
109
121
|
return {
|
|
110
|
-
schemaVersion:
|
|
122
|
+
schemaVersion: 3,
|
|
111
123
|
source: "testkit",
|
|
112
124
|
generatedAt: new Date(finishedAt).toISOString(),
|
|
113
125
|
product: {
|
|
@@ -134,19 +146,22 @@ export function buildRunArtifact({
|
|
|
134
146
|
summary: {
|
|
135
147
|
services: {
|
|
136
148
|
total: executed.length,
|
|
137
|
-
passed: executed.length - failedServices.length,
|
|
149
|
+
passed: executed.length - failedServices.length - effectivelySkippedServices.length,
|
|
138
150
|
failed: failedServices.length,
|
|
151
|
+
skipped: effectivelySkippedServices.length,
|
|
139
152
|
},
|
|
140
153
|
suites: {
|
|
141
154
|
total: totalSuites,
|
|
142
155
|
completed: completedSuites,
|
|
143
|
-
passed: completedSuites - failedSuites,
|
|
156
|
+
passed: completedSuites - failedSuites - skippedSuites,
|
|
144
157
|
failed: failedSuites,
|
|
158
|
+
skipped: skippedSuites,
|
|
145
159
|
},
|
|
146
160
|
files: {
|
|
147
161
|
total: totalFiles,
|
|
148
162
|
passed: passedFiles,
|
|
149
163
|
failed: failedFiles,
|
|
164
|
+
skipped: skippedFiles,
|
|
150
165
|
notRun: notRunFiles,
|
|
151
166
|
},
|
|
152
167
|
},
|
|
@@ -156,11 +171,13 @@ export function buildRunArtifact({
|
|
|
156
171
|
skipped: result.skipped,
|
|
157
172
|
suiteCount: result.suiteCount,
|
|
158
173
|
completedSuiteCount: result.completedSuiteCount,
|
|
174
|
+
skippedSuiteCount: result.skippedSuiteCount,
|
|
159
175
|
failedSuiteCount: result.failedSuiteCount,
|
|
160
176
|
totalFileCount: result.totalFileCount,
|
|
161
177
|
completedFileCount: result.completedFileCount,
|
|
162
178
|
passedFileCount: result.passedFileCount,
|
|
163
179
|
failedFileCount: result.failedFileCount,
|
|
180
|
+
skippedFileCount: result.skippedFileCount,
|
|
164
181
|
notRunFileCount: result.notRunFileCount,
|
|
165
182
|
durationMs: result.durationMs,
|
|
166
183
|
totalTaskDurationMs: result.totalTaskDurationMs,
|
|
@@ -170,3 +187,13 @@ export function buildRunArtifact({
|
|
|
170
187
|
})),
|
|
171
188
|
};
|
|
172
189
|
}
|
|
190
|
+
|
|
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
|
+
);
|
|
199
|
+
}
|
|
@@ -10,11 +10,13 @@ describe("runner reporting", () => {
|
|
|
10
10
|
skipped: false,
|
|
11
11
|
suiteCount: 1,
|
|
12
12
|
completedSuiteCount: 1,
|
|
13
|
+
skippedSuiteCount: 0,
|
|
13
14
|
failedSuiteCount: 0,
|
|
14
15
|
totalFileCount: 3,
|
|
15
16
|
completedFileCount: 3,
|
|
16
17
|
passedFileCount: 3,
|
|
17
18
|
failedFileCount: 0,
|
|
19
|
+
skippedFileCount: 0,
|
|
18
20
|
notRunFileCount: 0,
|
|
19
21
|
durationMs: 1200,
|
|
20
22
|
totalTaskDurationMs: 2400,
|
|
@@ -28,11 +30,13 @@ describe("runner reporting", () => {
|
|
|
28
30
|
skipped: true,
|
|
29
31
|
suiteCount: 0,
|
|
30
32
|
completedSuiteCount: 0,
|
|
33
|
+
skippedSuiteCount: 0,
|
|
31
34
|
failedSuiteCount: 0,
|
|
32
35
|
totalFileCount: 0,
|
|
33
36
|
completedFileCount: 0,
|
|
34
37
|
passedFileCount: 0,
|
|
35
38
|
failedFileCount: 0,
|
|
39
|
+
skippedFileCount: 0,
|
|
36
40
|
notRunFileCount: 0,
|
|
37
41
|
durationMs: 0,
|
|
38
42
|
totalTaskDurationMs: 0,
|
|
@@ -70,11 +74,25 @@ describe("runner reporting", () => {
|
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
expect(artifact.product.name).toBe("my-product");
|
|
73
|
-
expect(artifact.
|
|
77
|
+
expect(artifact.schemaVersion).toBe(3);
|
|
78
|
+
expect(artifact.summary.services).toEqual({
|
|
79
|
+
total: 1,
|
|
80
|
+
passed: 1,
|
|
81
|
+
failed: 0,
|
|
82
|
+
skipped: 0,
|
|
83
|
+
});
|
|
84
|
+
expect(artifact.summary.suites).toEqual({
|
|
85
|
+
total: 1,
|
|
86
|
+
completed: 1,
|
|
87
|
+
passed: 1,
|
|
88
|
+
failed: 0,
|
|
89
|
+
skipped: 0,
|
|
90
|
+
});
|
|
74
91
|
expect(artifact.summary.files).toEqual({
|
|
75
92
|
total: 3,
|
|
76
93
|
passed: 3,
|
|
77
94
|
failed: 0,
|
|
95
|
+
skipped: 0,
|
|
78
96
|
notRun: 0,
|
|
79
97
|
});
|
|
80
98
|
expect(artifact.services[0].durationMs).toBe(1200);
|
|
@@ -96,7 +114,11 @@ describe("runner reporting", () => {
|
|
|
96
114
|
framework: "k6",
|
|
97
115
|
files: [
|
|
98
116
|
{ path: "tests/api/integration/a.int.testkit.ts", status: "passed" },
|
|
99
|
-
{
|
|
117
|
+
{
|
|
118
|
+
path: "tests/api/integration/b.int.testkit.ts",
|
|
119
|
+
status: "skipped",
|
|
120
|
+
reason: "Billing is stubbed",
|
|
121
|
+
},
|
|
100
122
|
],
|
|
101
123
|
},
|
|
102
124
|
],
|
|
@@ -117,7 +139,7 @@ describe("runner reporting", () => {
|
|
|
117
139
|
});
|
|
118
140
|
|
|
119
141
|
expect(status).toEqual({
|
|
120
|
-
schemaVersion:
|
|
142
|
+
schemaVersion: 3,
|
|
121
143
|
source: "testkit",
|
|
122
144
|
notice: "Generated file. Do not edit manually.",
|
|
123
145
|
product: {
|
|
@@ -141,11 +163,13 @@ describe("runner reporting", () => {
|
|
|
141
163
|
total: 1,
|
|
142
164
|
passed: 0,
|
|
143
165
|
failed: 1,
|
|
166
|
+
skipped: 0,
|
|
144
167
|
},
|
|
145
168
|
tests: {
|
|
146
169
|
total: 2,
|
|
147
170
|
passed: 1,
|
|
148
|
-
failed:
|
|
171
|
+
failed: 0,
|
|
172
|
+
skipped: 1,
|
|
149
173
|
notRun: 0,
|
|
150
174
|
},
|
|
151
175
|
},
|
|
@@ -160,7 +184,8 @@ describe("runner reporting", () => {
|
|
|
160
184
|
service: "api",
|
|
161
185
|
type: "int",
|
|
162
186
|
path: "tests/api/integration/b.int.testkit.ts",
|
|
163
|
-
status: "
|
|
187
|
+
status: "skipped",
|
|
188
|
+
reason: "Billing is stubbed",
|
|
164
189
|
},
|
|
165
190
|
],
|
|
166
191
|
});
|
package/lib/runner/results.mjs
CHANGED
|
@@ -29,12 +29,12 @@ export function buildServiceTrackers(servicePlans, startedAt) {
|
|
|
29
29
|
displayType: suite.displayType || suite.type,
|
|
30
30
|
framework: suite.framework,
|
|
31
31
|
orderIndex: suite.orderIndex,
|
|
32
|
-
fileCount: suite.files.length,
|
|
32
|
+
fileCount: suite.totalFileCount ?? suite.files.length,
|
|
33
33
|
completedFileCount: 0,
|
|
34
34
|
failedFiles: [],
|
|
35
35
|
failedFileSet: new Set(),
|
|
36
|
-
fileResultsByPath: new Map(
|
|
37
|
-
suite.files.map((file) => {
|
|
36
|
+
fileResultsByPath: new Map([
|
|
37
|
+
...suite.files.map((file) => {
|
|
38
38
|
const normalizedPath = normalizePathSeparators(file);
|
|
39
39
|
return [
|
|
40
40
|
normalizedPath,
|
|
@@ -43,11 +43,25 @@ export function buildServiceTrackers(servicePlans, startedAt) {
|
|
|
43
43
|
failed: false,
|
|
44
44
|
durationMs: 0,
|
|
45
45
|
error: null,
|
|
46
|
+
reason: null,
|
|
46
47
|
status: "not_run",
|
|
48
|
+
artifacts: [],
|
|
47
49
|
},
|
|
48
50
|
];
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
+
}),
|
|
52
|
+
...(suite.skippedFiles || []).map((file) => [
|
|
53
|
+
file.path,
|
|
54
|
+
{
|
|
55
|
+
path: file.path,
|
|
56
|
+
failed: false,
|
|
57
|
+
durationMs: 0,
|
|
58
|
+
error: null,
|
|
59
|
+
reason: file.reason,
|
|
60
|
+
status: "skipped",
|
|
61
|
+
artifacts: [],
|
|
62
|
+
},
|
|
63
|
+
]),
|
|
64
|
+
]),
|
|
51
65
|
durationMs: 0,
|
|
52
66
|
error: null,
|
|
53
67
|
}));
|
|
@@ -93,25 +107,32 @@ export function recordTaskOutcome(trackers, task, outcome, finishedAt = Date.now
|
|
|
93
107
|
: Math.max(tracker.lastTaskAt, outcomeFinishedAt);
|
|
94
108
|
tracker.totalTaskDurationMs += outcomeDurationMs;
|
|
95
109
|
|
|
96
|
-
suite.completedFileCount += 1;
|
|
97
110
|
suite.durationMs += outcomeDurationMs;
|
|
98
111
|
const normalizedPath = normalizePathSeparators(task.file);
|
|
99
112
|
const existingFileResult = suite.fileResultsByPath.get(normalizedPath);
|
|
113
|
+
const status = normalizeOutcomeStatus(outcome);
|
|
114
|
+
if (status !== "skipped") {
|
|
115
|
+
suite.completedFileCount += 1;
|
|
116
|
+
}
|
|
100
117
|
if (existingFileResult) {
|
|
101
|
-
existingFileResult.failed =
|
|
118
|
+
existingFileResult.failed = status === "failed";
|
|
102
119
|
existingFileResult.durationMs = outcomeDurationMs;
|
|
103
120
|
existingFileResult.error = outcome.error;
|
|
104
|
-
existingFileResult.
|
|
121
|
+
existingFileResult.reason = outcome.reason || null;
|
|
122
|
+
existingFileResult.status = status;
|
|
123
|
+
existingFileResult.artifacts = Array.isArray(outcome.artifacts) ? outcome.artifacts : [];
|
|
105
124
|
} else {
|
|
106
125
|
suite.fileResultsByPath.set(normalizedPath, {
|
|
107
126
|
path: normalizedPath,
|
|
108
|
-
failed:
|
|
127
|
+
failed: status === "failed",
|
|
109
128
|
durationMs: outcomeDurationMs,
|
|
110
129
|
error: outcome.error,
|
|
111
|
-
|
|
130
|
+
reason: outcome.reason || null,
|
|
131
|
+
status,
|
|
132
|
+
artifacts: Array.isArray(outcome.artifacts) ? outcome.artifacts : [],
|
|
112
133
|
});
|
|
113
134
|
}
|
|
114
|
-
if (
|
|
135
|
+
if (status === "failed" && !suite.failedFileSet.has(task.file)) {
|
|
115
136
|
suite.failedFileSet.add(task.file);
|
|
116
137
|
suite.failedFiles.push(task.file);
|
|
117
138
|
}
|
|
@@ -145,7 +166,14 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
145
166
|
skipped: true,
|
|
146
167
|
suiteCount: 0,
|
|
147
168
|
completedSuiteCount: 0,
|
|
169
|
+
skippedSuiteCount: 0,
|
|
148
170
|
failedSuiteCount: 0,
|
|
171
|
+
totalFileCount: 0,
|
|
172
|
+
completedFileCount: 0,
|
|
173
|
+
passedFileCount: 0,
|
|
174
|
+
failedFileCount: 0,
|
|
175
|
+
skippedFileCount: 0,
|
|
176
|
+
notRunFileCount: 0,
|
|
149
177
|
durationMs: 0,
|
|
150
178
|
totalTaskDurationMs: 0,
|
|
151
179
|
suites: [],
|
|
@@ -158,14 +186,18 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
158
186
|
.map((suite) => finalizeSuite(suite));
|
|
159
187
|
|
|
160
188
|
const completedSuiteCount = suites.filter(
|
|
161
|
-
(suite) => suite.completedFileCount === suite.fileCount
|
|
189
|
+
(suite) => suite.completedFileCount + suite.skippedFileCount === suite.fileCount
|
|
190
|
+
).length;
|
|
191
|
+
const skippedSuiteCount = suites.filter(
|
|
192
|
+
(suite) => suite.skippedFileCount === suite.fileCount && suite.fileCount > 0
|
|
162
193
|
).length;
|
|
163
194
|
const failedSuiteCount = suites.filter((suite) => suite.failedFileCount > 0).length;
|
|
164
195
|
const totalFileCount = suites.reduce((sum, suite) => sum + suite.fileCount, 0);
|
|
165
196
|
const completedFileCount = suites.reduce((sum, suite) => sum + suite.completedFileCount, 0);
|
|
166
197
|
const failedFileCount = suites.reduce((sum, suite) => sum + suite.failedFileCount, 0);
|
|
167
198
|
const passedFileCount = suites.reduce((sum, suite) => sum + suite.passedFileCount, 0);
|
|
168
|
-
const
|
|
199
|
+
const skippedFileCount = suites.reduce((sum, suite) => sum + suite.skippedFileCount, 0);
|
|
200
|
+
const notRunFileCount = totalFileCount - completedFileCount - skippedFileCount;
|
|
169
201
|
const totalTaskDurationMs =
|
|
170
202
|
tracker.totalTaskDurationMs || suites.reduce((sum, suite) => sum + suite.durationMs, 0);
|
|
171
203
|
const durationMs =
|
|
@@ -183,11 +215,13 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
183
215
|
skipped: false,
|
|
184
216
|
suiteCount: tracker.suiteCount,
|
|
185
217
|
completedSuiteCount,
|
|
218
|
+
skippedSuiteCount,
|
|
186
219
|
failedSuiteCount,
|
|
187
220
|
totalFileCount,
|
|
188
221
|
completedFileCount,
|
|
189
222
|
passedFileCount,
|
|
190
223
|
failedFileCount,
|
|
224
|
+
skippedFileCount,
|
|
191
225
|
notRunFileCount,
|
|
192
226
|
durationMs,
|
|
193
227
|
totalTaskDurationMs,
|
|
@@ -212,6 +246,10 @@ function finalizeSuite(suite) {
|
|
|
212
246
|
status: file.status,
|
|
213
247
|
durationMs: file.durationMs,
|
|
214
248
|
error: file.error,
|
|
249
|
+
reason: file.reason,
|
|
250
|
+
...(Array.isArray(file.artifacts) && file.artifacts.length > 0
|
|
251
|
+
? { artifacts: file.artifacts }
|
|
252
|
+
: {}),
|
|
215
253
|
}));
|
|
216
254
|
|
|
217
255
|
return {
|
|
@@ -223,7 +261,11 @@ function finalizeSuite(suite) {
|
|
|
223
261
|
completedFileCount: suite.completedFileCount,
|
|
224
262
|
passedFileCount: files.filter((file) => file.status === "passed").length,
|
|
225
263
|
failedFileCount: suite.failedFiles.length,
|
|
226
|
-
|
|
264
|
+
skippedFileCount: files.filter((file) => file.status === "skipped").length,
|
|
265
|
+
notRunFileCount:
|
|
266
|
+
suite.fileCount -
|
|
267
|
+
suite.completedFileCount -
|
|
268
|
+
files.filter((file) => file.status === "skipped").length,
|
|
227
269
|
failedFiles: suite.failedFiles,
|
|
228
270
|
durationMs: suite.durationMs,
|
|
229
271
|
error: suite.error,
|
|
@@ -239,3 +281,8 @@ function formatFrameworkForArtifact(framework) {
|
|
|
239
281
|
if (framework === "k6") return "default";
|
|
240
282
|
return framework;
|
|
241
283
|
}
|
|
284
|
+
|
|
285
|
+
function normalizeOutcomeStatus(outcome) {
|
|
286
|
+
if (outcome?.status === "skipped") return "skipped";
|
|
287
|
+
return outcome?.failed ? "failed" : "passed";
|
|
288
|
+
}
|