@elench/testkit 0.1.16 → 0.1.18
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 +44 -19
- package/bin/testkit.mjs +1 -1
- package/lib/cli/args.mjs +57 -0
- package/lib/cli/args.test.mjs +62 -0
- package/lib/cli/index.mjs +88 -0
- package/lib/config/index.mjs +294 -0
- package/lib/config/index.test.mjs +12 -0
- package/lib/config/model.mjs +422 -0
- package/lib/config/model.test.mjs +193 -0
- package/lib/database/fingerprint.mjs +61 -0
- package/lib/database/fingerprint.test.mjs +93 -0
- package/lib/{database.mjs → database/index.mjs} +45 -160
- package/lib/database/naming.mjs +47 -0
- package/lib/database/naming.test.mjs +39 -0
- package/lib/database/state.mjs +52 -0
- package/lib/database/state.test.mjs +66 -0
- package/lib/reporters/playwright.mjs +125 -0
- package/lib/reporters/playwright.test.mjs +73 -0
- package/lib/runner/index.mjs +1221 -0
- package/lib/runner/metadata.mjs +55 -0
- package/lib/runner/metadata.test.mjs +52 -0
- package/lib/runner/planning.mjs +270 -0
- package/lib/runner/planning.test.mjs +127 -0
- package/lib/runner/results.mjs +285 -0
- package/lib/runner/results.test.mjs +144 -0
- package/lib/runner/state.mjs +71 -0
- package/lib/runner/state.test.mjs +64 -0
- package/lib/runner/template.mjs +320 -0
- package/lib/runner/template.test.mjs +150 -0
- package/lib/telemetry/index.mjs +43 -0
- package/lib/timing/index.mjs +73 -0
- package/lib/timing/index.test.mjs +64 -0
- package/package.json +11 -3
- package/infra/neon-down.sh +0 -18
- package/infra/neon-up.sh +0 -124
- package/lib/cli.mjs +0 -132
- package/lib/config.mjs +0 -666
- package/lib/exec.mjs +0 -20
- package/lib/runner.mjs +0 -1165
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
export function parsePlaywrightJsonResults(stdout, cwd) {
|
|
4
|
+
if (!stdout.trim()) {
|
|
5
|
+
return { fileResults: new Map(), errors: [] };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = JSON.parse(stdout);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
return {
|
|
13
|
+
fileResults: new Map(),
|
|
14
|
+
errors: [`Could not parse Playwright JSON output: ${formatError(error)}`],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fileResults = new Map();
|
|
19
|
+
visitPlaywrightSuites(parsed.suites || [], null, fileResults, cwd);
|
|
20
|
+
return {
|
|
21
|
+
fileResults,
|
|
22
|
+
errors: (parsed.errors || []).map(formatPlaywrightReporterError).filter(Boolean),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function visitPlaywrightSuites(suites, inheritedFile, fileResults, cwd) {
|
|
27
|
+
for (const suite of suites || []) {
|
|
28
|
+
const suiteFile = normalizeReportedFile(extractReporterFile(suite) || inheritedFile, cwd);
|
|
29
|
+
for (const child of suite.suites || []) {
|
|
30
|
+
visitPlaywrightSuites([child], suiteFile, fileResults, cwd);
|
|
31
|
+
}
|
|
32
|
+
for (const spec of suite.specs || []) {
|
|
33
|
+
collectPlaywrightSpec(spec, suiteFile, fileResults, cwd);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function collectPlaywrightSpec(spec, inheritedFile, fileResults, cwd) {
|
|
39
|
+
const file = normalizeReportedFile(extractReporterFile(spec) || inheritedFile, cwd);
|
|
40
|
+
if (!file) return;
|
|
41
|
+
|
|
42
|
+
const current = fileResults.get(file) || {
|
|
43
|
+
failed: false,
|
|
44
|
+
error: null,
|
|
45
|
+
durationMs: 0,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (const test of spec.tests || []) {
|
|
49
|
+
const results = Array.isArray(test.results) ? test.results : [];
|
|
50
|
+
current.durationMs += results.reduce(
|
|
51
|
+
(sum, result) => sum + Number(result?.duration || 0),
|
|
52
|
+
0
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const final = choosePlaywrightFinalResult(results);
|
|
56
|
+
const failed =
|
|
57
|
+
test.outcome === "unexpected" ||
|
|
58
|
+
!isPlaywrightPassingStatus(final?.status);
|
|
59
|
+
|
|
60
|
+
if (failed) {
|
|
61
|
+
current.failed = true;
|
|
62
|
+
current.error ||= extractPlaywrightFailure(final, spec, test);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fileResults.set(file, current);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function choosePlaywrightFinalResult(results) {
|
|
70
|
+
if (!results || results.length === 0) return null;
|
|
71
|
+
return results[results.length - 1];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isPlaywrightPassingStatus(status) {
|
|
75
|
+
return !status || ["passed", "skipped", "expected"].includes(status);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function extractPlaywrightFailure(finalResult, spec, test) {
|
|
79
|
+
const fromResult =
|
|
80
|
+
finalResult?.error?.message ||
|
|
81
|
+
finalResult?.error?.value ||
|
|
82
|
+
finalResult?.error?.stack;
|
|
83
|
+
if (fromResult) return firstLine(fromResult);
|
|
84
|
+
|
|
85
|
+
const fromTest = test?.errors?.[0]?.message;
|
|
86
|
+
if (fromTest) return firstLine(fromTest);
|
|
87
|
+
|
|
88
|
+
return firstLine(spec?.title || "Playwright test failed");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function formatPlaywrightReporterError(error) {
|
|
92
|
+
if (!error) return null;
|
|
93
|
+
if (typeof error === "string") return firstLine(error);
|
|
94
|
+
if (typeof error.message === "string") return firstLine(error.message);
|
|
95
|
+
if (typeof error.value === "string") return firstLine(error.value);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function extractReporterFile(node) {
|
|
100
|
+
if (!node || typeof node !== "object") return null;
|
|
101
|
+
if (typeof node.file === "string" && node.file.length > 0) return node.file;
|
|
102
|
+
if (node.location && typeof node.location.file === "string" && node.location.file.length > 0) {
|
|
103
|
+
return node.location.file;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function normalizeReportedFile(filePath, cwd) {
|
|
109
|
+
if (!filePath) return null;
|
|
110
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
111
|
+
return normalizePathSeparators(path.relative(cwd, absolute));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function firstLine(value) {
|
|
115
|
+
return String(value).split(/\r?\n/).find((line) => line.trim().length > 0)?.trim() || null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizePathSeparators(filePath) {
|
|
119
|
+
return filePath.split(path.sep).join("/");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatError(error) {
|
|
123
|
+
if (error instanceof Error) return error.message;
|
|
124
|
+
return String(error);
|
|
125
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
choosePlaywrightFinalResult,
|
|
4
|
+
extractPlaywrightFailure,
|
|
5
|
+
parsePlaywrightJsonResults,
|
|
6
|
+
} from "./playwright.mjs";
|
|
7
|
+
|
|
8
|
+
describe("playwright-report", () => {
|
|
9
|
+
it("handles empty and invalid output", () => {
|
|
10
|
+
expect(parsePlaywrightJsonResults("", "/tmp")).toEqual({
|
|
11
|
+
fileResults: new Map(),
|
|
12
|
+
errors: [],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const invalid = parsePlaywrightJsonResults("{", "/tmp");
|
|
16
|
+
expect(invalid.errors[0]).toContain("Could not parse Playwright JSON output");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("collects nested suite results", () => {
|
|
20
|
+
const stdout = JSON.stringify({
|
|
21
|
+
errors: [{ message: "reporter failure" }],
|
|
22
|
+
suites: [
|
|
23
|
+
{
|
|
24
|
+
file: "/tmp/tests/auth.spec.js",
|
|
25
|
+
specs: [
|
|
26
|
+
{
|
|
27
|
+
title: "auth works",
|
|
28
|
+
tests: [
|
|
29
|
+
{
|
|
30
|
+
outcome: "unexpected",
|
|
31
|
+
results: [
|
|
32
|
+
{
|
|
33
|
+
status: "failed",
|
|
34
|
+
duration: 15,
|
|
35
|
+
error: {
|
|
36
|
+
message: "boom\nstack",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const parsed = parsePlaywrightJsonResults(stdout, "/tmp");
|
|
49
|
+
expect(parsed.errors).toEqual(["reporter failure"]);
|
|
50
|
+
expect(parsed.fileResults.get("tests/auth.spec.js")).toEqual({
|
|
51
|
+
failed: true,
|
|
52
|
+
error: "boom",
|
|
53
|
+
durationMs: 15,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("chooses the final result and extracts failures", () => {
|
|
58
|
+
expect(
|
|
59
|
+
choosePlaywrightFinalResult([
|
|
60
|
+
{ status: "failed" },
|
|
61
|
+
{ status: "passed" },
|
|
62
|
+
])
|
|
63
|
+
).toEqual({ status: "passed" });
|
|
64
|
+
|
|
65
|
+
expect(
|
|
66
|
+
extractPlaywrightFailure(
|
|
67
|
+
{ error: { message: "first line\nsecond line" } },
|
|
68
|
+
{ title: "spec title" },
|
|
69
|
+
{ errors: [] }
|
|
70
|
+
)
|
|
71
|
+
).toBe("first line");
|
|
72
|
+
});
|
|
73
|
+
});
|