@aplaytest/runner-playwright 0.1.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/.tsbuildinfo +1 -0
- package/dist/api-fixtures.d.ts +71 -0
- package/dist/api-fixtures.d.ts.map +1 -0
- package/dist/api-fixtures.js +162 -0
- package/dist/api-fixtures.js.map +1 -0
- package/dist/assemble.d.ts +86 -0
- package/dist/assemble.d.ts.map +1 -0
- package/dist/assemble.js +176 -0
- package/dist/assemble.js.map +1 -0
- package/dist/bind.d.ts +43 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +112 -0
- package/dist/bind.js.map +1 -0
- package/dist/fixtures.d.ts +54 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +210 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.d.ts +79 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +277 -0
- package/dist/reporter.js.map +1 -0
- package/dist/sidecar.d.ts +134 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +111 -0
- package/dist/sidecar.js.map +1 -0
- package/dist/spawn.d.ts +57 -0
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +170 -0
- package/dist/spawn.js.map +1 -0
- package/dist/steps.d.ts +45 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +106 -0
- package/dist/steps.js.map +1 -0
- package/dist-cjs/.tsbuildinfo +1 -0
- package/dist-cjs/api-fixtures.js +166 -0
- package/dist-cjs/api-fixtures.js.map +1 -0
- package/dist-cjs/assemble.js +181 -0
- package/dist-cjs/assemble.js.map +1 -0
- package/dist-cjs/bind.js +117 -0
- package/dist-cjs/bind.js.map +1 -0
- package/dist-cjs/fixtures.js +215 -0
- package/dist-cjs/fixtures.js.map +1 -0
- package/dist-cjs/index.js +64 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/reporter.js +282 -0
- package/dist-cjs/reporter.js.map +1 -0
- package/dist-cjs/sidecar.js +116 -0
- package/dist-cjs/sidecar.js.map +1 -0
- package/dist-cjs/spawn.js +174 -0
- package/dist-cjs/spawn.js.map +1 -0
- package/dist-cjs/steps.js +112 -0
- package/dist-cjs/steps.js.map +1 -0
- package/package.json +51 -0
package/dist/sidecar.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixture → reporter channel.
|
|
3
|
+
*
|
|
4
|
+
* A Playwright reporter runs in the main process and cannot reach into the
|
|
5
|
+
* browser. Anything only the worker knows — the ARIA snapshot, the request
|
|
6
|
+
* ledger, console output — has to travel as a test ATTACHMENT, which is the
|
|
7
|
+
* runner's own supported channel and needs no side files or IPC.
|
|
8
|
+
*
|
|
9
|
+
* Both ends import these schemas, and the reporter PARSES rather than casts.
|
|
10
|
+
* A fixture that drifts from the contract fails loudly with a named error
|
|
11
|
+
* instead of silently producing a bundle with an empty ARIA snapshot — the
|
|
12
|
+
* same reasoning behind validating mocked response bodies against wire
|
|
13
|
+
* schemas rather than trusting them.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
export const SIDECAR = {
|
|
17
|
+
page: 'atest:page',
|
|
18
|
+
network: 'atest:network',
|
|
19
|
+
console: 'atest:console',
|
|
20
|
+
intent: 'atest:intent',
|
|
21
|
+
coverage: 'atest:coverage',
|
|
22
|
+
};
|
|
23
|
+
export const PageSidecarSchema = z.object({
|
|
24
|
+
url: z.string(),
|
|
25
|
+
title: z.string(),
|
|
26
|
+
ariaSnapshot: z.string(),
|
|
27
|
+
testIdsPresent: z.array(z.string()),
|
|
28
|
+
htmlDigest: z.string().nullable().default(null),
|
|
29
|
+
});
|
|
30
|
+
const RequestRecordSchema = z.object({
|
|
31
|
+
url: z.string(),
|
|
32
|
+
method: z.string(),
|
|
33
|
+
status: z.number().nullable(),
|
|
34
|
+
durationMs: z.number(),
|
|
35
|
+
failureText: z.string().nullable().default(null),
|
|
36
|
+
schemaError: z.string().nullable().default(null),
|
|
37
|
+
});
|
|
38
|
+
export const NetworkSidecarSchema = z.object({
|
|
39
|
+
failed: z.array(RequestRecordSchema),
|
|
40
|
+
slow: z.array(RequestRecordSchema),
|
|
41
|
+
statusCounts: z.record(z.string(), z.number()),
|
|
42
|
+
});
|
|
43
|
+
export const ConsoleSidecarSchema = z.object({
|
|
44
|
+
errors: z.array(z.string()),
|
|
45
|
+
warnings: z.array(z.string()),
|
|
46
|
+
});
|
|
47
|
+
export const IntentSidecarSchema = z.object({
|
|
48
|
+
selector: z.string().nullable().default(null),
|
|
49
|
+
selectorSource: z
|
|
50
|
+
.object({
|
|
51
|
+
file: z.string(),
|
|
52
|
+
line: z.number(),
|
|
53
|
+
constantPath: z.string(),
|
|
54
|
+
aliases: z.array(z.string()).default([]),
|
|
55
|
+
})
|
|
56
|
+
.nullable()
|
|
57
|
+
.default(null),
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Routes a test actually visited.
|
|
61
|
+
*
|
|
62
|
+
* The one signal that survives a fixture barrel. Static imports say every spec
|
|
63
|
+
* depends on every feature (they compose one `test` object); what a test
|
|
64
|
+
* VISITED is independent of that, and is the only thing that can narrow
|
|
65
|
+
* selection in a suite built that way.
|
|
66
|
+
*/
|
|
67
|
+
export const CoverageSidecarSchema = z.object({
|
|
68
|
+
routes: z.array(z.string()),
|
|
69
|
+
});
|
|
70
|
+
const SCHEMAS = {
|
|
71
|
+
[SIDECAR.page]: PageSidecarSchema,
|
|
72
|
+
[SIDECAR.network]: NetworkSidecarSchema,
|
|
73
|
+
[SIDECAR.console]: ConsoleSidecarSchema,
|
|
74
|
+
[SIDECAR.intent]: IntentSidecarSchema,
|
|
75
|
+
[SIDECAR.coverage]: CoverageSidecarSchema,
|
|
76
|
+
};
|
|
77
|
+
export class SidecarParseError extends Error {
|
|
78
|
+
sidecar;
|
|
79
|
+
issues;
|
|
80
|
+
constructor(sidecar, issues) {
|
|
81
|
+
super(`Attachment "${sidecar}" does not match its schema — the atest fixtures and ` +
|
|
82
|
+
`reporter are out of sync.\n${issues}`);
|
|
83
|
+
this.sidecar = sidecar;
|
|
84
|
+
this.issues = issues;
|
|
85
|
+
this.name = 'SidecarParseError';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse a sidecar payload. Absent is fine (the fixtures are optional);
|
|
90
|
+
* PRESENT-BUT-WRONG is not, and throws.
|
|
91
|
+
*/
|
|
92
|
+
export function parseSidecar(name, raw) {
|
|
93
|
+
if (raw === undefined)
|
|
94
|
+
return null;
|
|
95
|
+
let json;
|
|
96
|
+
try {
|
|
97
|
+
json = JSON.parse(raw);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new SidecarParseError(name, error instanceof Error ? error.message : String(error));
|
|
101
|
+
}
|
|
102
|
+
const result = SCHEMAS[name].safeParse(json);
|
|
103
|
+
if (!result.success) {
|
|
104
|
+
const issues = result.error.issues
|
|
105
|
+
.map(i => ` • ${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
106
|
+
.join('\n');
|
|
107
|
+
throw new SidecarParseError(name, issues);
|
|
108
|
+
}
|
|
109
|
+
return result.data;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=sidecar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,eAAe;IACxB,MAAM,EAAE,cAAc;IACtB,QAAQ,EAAE,gBAAgB;CAClB,CAAC;AAIX,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACnC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CAChD,CAAC,CAAC;AAGH,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAChD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CACjD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;IAClC,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;CAC/C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC9B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC;SACd,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;KACzC,CAAC;SACD,QAAQ,EAAE;SACV,OAAO,CAAC,IAAI,CAAC;CACjB,CAAC,CAAC;AAGH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC5B,CAAC,CAAC;AAGH,MAAM,OAAO,GAAG;IACd,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,iBAAiB;IACjC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,oBAAoB;IACvC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,oBAAoB;IACvC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,mBAAmB;IACrC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,qBAAqB;CACjC,CAAC;AAEX,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAE/B;IACA;IAFX,YACW,OAAe,EACf,MAAc;QAEvB,KAAK,CACH,eAAe,OAAO,uDAAuD;YAC3E,8BAA8B,MAAM,EAAE,CACzC,CAAC;QANO,YAAO,GAAP,OAAO,CAAQ;QACf,WAAM,GAAN,MAAM,CAAQ;QAMvB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAO,EACP,GAAuB;IAEvB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnC,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,iBAAiB,CAAC,IAAI,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC7D,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC,IAAoC,CAAC;AACrD,CAAC"}
|
package/dist/spawn.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive `playwright test` as a child process and read structured results.
|
|
3
|
+
*
|
|
4
|
+
* Used by bisect (re-run under controlled perturbations) and by heal
|
|
5
|
+
* validation (re-run with a candidate patch applied). Both need counts, not
|
|
6
|
+
* evidence, so this uses Playwright's own JSON reporter rather than ours —
|
|
7
|
+
* fewer moving parts, and it works against a consumer repo whether or not the
|
|
8
|
+
* atest reporter is configured.
|
|
9
|
+
*/
|
|
10
|
+
export interface PlaywrightRunOptions {
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly config?: string | undefined;
|
|
13
|
+
readonly file?: string | undefined;
|
|
14
|
+
/** Matched against the test title. Escaped for you — pass plain text. */
|
|
15
|
+
readonly grepTitle?: string | undefined;
|
|
16
|
+
readonly project?: string | undefined;
|
|
17
|
+
readonly workers?: number | undefined;
|
|
18
|
+
readonly repeatEach?: number | undefined;
|
|
19
|
+
readonly maxFailures?: number | undefined;
|
|
20
|
+
readonly timeoutMs?: number | undefined;
|
|
21
|
+
readonly env?: Readonly<Record<string, string>> | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Per-test outcomes, so a caller can measure ONE test inside a wider run. */
|
|
24
|
+
export interface SpecOutcome {
|
|
25
|
+
readonly title: string;
|
|
26
|
+
readonly file: string;
|
|
27
|
+
readonly passed: number;
|
|
28
|
+
readonly failed: number;
|
|
29
|
+
/** Last failure message, when any result failed. Used to spot env errors. */
|
|
30
|
+
readonly error: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface PlaywrightRunResult {
|
|
33
|
+
/** Every test passed. */
|
|
34
|
+
readonly ok: boolean;
|
|
35
|
+
readonly passed: number;
|
|
36
|
+
readonly failed: number;
|
|
37
|
+
readonly flaky: number;
|
|
38
|
+
readonly skipped: number;
|
|
39
|
+
readonly durationMs: number;
|
|
40
|
+
readonly exitCode: number;
|
|
41
|
+
/** True when the run could not be parsed — treated as inconclusive, not as failure. */
|
|
42
|
+
readonly inconclusive: boolean;
|
|
43
|
+
readonly stderr: string;
|
|
44
|
+
/**
|
|
45
|
+
* Outcomes broken down per test.
|
|
46
|
+
*
|
|
47
|
+
* Essential whenever a run executes more tests than the one being measured:
|
|
48
|
+
* aggregate stats would attribute a NEIGHBOUR's failures to the test under
|
|
49
|
+
* examination, which is exactly how a co-scheduling probe manufactures a
|
|
50
|
+
* false "test pollution" verdict.
|
|
51
|
+
*/
|
|
52
|
+
readonly specs: readonly SpecOutcome[];
|
|
53
|
+
}
|
|
54
|
+
/** Playwright's `-g` takes a regular expression; titles are literal text. */
|
|
55
|
+
export declare function escapeForGrep(title: string): string;
|
|
56
|
+
export declare function runPlaywright(options: PlaywrightRunOptions): Promise<PlaywrightRunResult>;
|
|
57
|
+
//# sourceMappingURL=spawn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;CAC7D;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,yBAAyB;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CACxC;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAyGD,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8E/F"}
|
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive `playwright test` as a child process and read structured results.
|
|
3
|
+
*
|
|
4
|
+
* Used by bisect (re-run under controlled perturbations) and by heal
|
|
5
|
+
* validation (re-run with a candidate patch applied). Both need counts, not
|
|
6
|
+
* evidence, so this uses Playwright's own JSON reporter rather than ours —
|
|
7
|
+
* fewer moving parts, and it works against a consumer repo whether or not the
|
|
8
|
+
* atest reporter is configured.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
/** Playwright's `-g` takes a regular expression; titles are literal text. */
|
|
15
|
+
export function escapeForGrep(title) {
|
|
16
|
+
return title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
17
|
+
}
|
|
18
|
+
const RESULT_PASSED = 'passed';
|
|
19
|
+
const RESULT_FAILED = 'failed';
|
|
20
|
+
const RESULT_TIMED_OUT = 'timedOut';
|
|
21
|
+
const PLAYWRIGHT_JSON_OUTPUT = 'PLAYWRIGHT_JSON_OUTPUT_NAME';
|
|
22
|
+
function countResults(spec) {
|
|
23
|
+
let passed = 0;
|
|
24
|
+
let failed = 0;
|
|
25
|
+
let error = null;
|
|
26
|
+
for (const test of spec.tests ?? []) {
|
|
27
|
+
for (const result of test.results ?? []) {
|
|
28
|
+
if (result.status === RESULT_PASSED)
|
|
29
|
+
passed += 1;
|
|
30
|
+
else if (result.status === RESULT_FAILED || result.status === RESULT_TIMED_OUT) {
|
|
31
|
+
failed += 1;
|
|
32
|
+
error = result.error?.message ?? error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { passed, failed, error };
|
|
37
|
+
}
|
|
38
|
+
function collectSpecs(suites, file = '') {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const suite of suites ?? []) {
|
|
41
|
+
const suiteFile = suite.file ?? file;
|
|
42
|
+
for (const spec of suite.specs ?? []) {
|
|
43
|
+
out.push({ title: spec.title ?? '', file: suiteFile, ...countResults(spec) });
|
|
44
|
+
}
|
|
45
|
+
out.push(...collectSpecs(suite.suites, suiteFile));
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function buildArgs(options) {
|
|
50
|
+
const args = ['playwright', 'test'];
|
|
51
|
+
if (options.config !== undefined)
|
|
52
|
+
args.push('--config', options.config);
|
|
53
|
+
if (options.file !== undefined)
|
|
54
|
+
args.push(options.file);
|
|
55
|
+
if (options.grepTitle !== undefined)
|
|
56
|
+
args.push('-g', escapeForGrep(options.grepTitle));
|
|
57
|
+
if (options.project !== undefined)
|
|
58
|
+
args.push('--project', options.project);
|
|
59
|
+
if (options.workers !== undefined)
|
|
60
|
+
args.push(`--workers=${options.workers}`);
|
|
61
|
+
if (options.repeatEach !== undefined)
|
|
62
|
+
args.push(`--repeat-each=${options.repeatEach}`);
|
|
63
|
+
if (options.maxFailures !== undefined)
|
|
64
|
+
args.push(`--max-failures=${options.maxFailures}`);
|
|
65
|
+
// Replaces the project's reporters for this invocation. Deliberate: bisect
|
|
66
|
+
// and validation want counts, and inheriting a consumer's HTML or blob
|
|
67
|
+
// reporter would write artifacts nobody asked for on every probe.
|
|
68
|
+
args.push('--reporter=json');
|
|
69
|
+
return args;
|
|
70
|
+
}
|
|
71
|
+
function isJsonReport(value) {
|
|
72
|
+
return typeof value === 'object' && value !== null;
|
|
73
|
+
}
|
|
74
|
+
function parseJsonReport(raw) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(raw);
|
|
77
|
+
return isJsonReport(parsed) ? parsed : null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function inconclusiveResult(exitCode, stderr) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
passed: 0,
|
|
87
|
+
failed: 0,
|
|
88
|
+
flaky: 0,
|
|
89
|
+
skipped: 0,
|
|
90
|
+
durationMs: 0,
|
|
91
|
+
exitCode,
|
|
92
|
+
inconclusive: true,
|
|
93
|
+
stderr,
|
|
94
|
+
specs: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export async function runPlaywright(options) {
|
|
98
|
+
const dir = await mkdtemp(join(tmpdir(), 'atest-run-'));
|
|
99
|
+
const jsonPath = join(dir, 'report.json');
|
|
100
|
+
try {
|
|
101
|
+
const args = buildArgs(options);
|
|
102
|
+
const result = await new Promise(resolve => {
|
|
103
|
+
const child = spawn('npx', args, {
|
|
104
|
+
cwd: options.cwd,
|
|
105
|
+
env: {
|
|
106
|
+
...process.env,
|
|
107
|
+
...options.env,
|
|
108
|
+
[PLAYWRIGHT_JSON_OUTPUT]: jsonPath,
|
|
109
|
+
// The reporter is not wanted during a probe: bisect runs the same
|
|
110
|
+
// test dozens of times, and each run would otherwise write evidence
|
|
111
|
+
// bundles that pollute the history the analysis is reading.
|
|
112
|
+
ATEST: '0',
|
|
113
|
+
FORCE_COLOR: '0',
|
|
114
|
+
},
|
|
115
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
116
|
+
});
|
|
117
|
+
let stderr = '';
|
|
118
|
+
child.stderr.on('data', (chunk) => {
|
|
119
|
+
stderr += chunk.toString('utf8');
|
|
120
|
+
});
|
|
121
|
+
const timeout = options.timeoutMs === undefined
|
|
122
|
+
? null
|
|
123
|
+
: setTimeout(() => child.kill('SIGTERM'), options.timeoutMs);
|
|
124
|
+
child.on('close', code => {
|
|
125
|
+
if (timeout !== null)
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
resolve({ code: code ?? 1, stderr });
|
|
128
|
+
});
|
|
129
|
+
child.on('error', error => {
|
|
130
|
+
if (timeout !== null)
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
resolve({ code: 1, stderr: error.message });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
const raw = await readFile(jsonPath, 'utf8').catch(() => null);
|
|
136
|
+
if (raw === null) {
|
|
137
|
+
// No report means the run never got as far as executing tests — a config
|
|
138
|
+
// error, a missing browser. That is INCONCLUSIVE, not a failing test;
|
|
139
|
+
// counting it as a failure would make bisect blame the code under test
|
|
140
|
+
// for a broken environment.
|
|
141
|
+
return inconclusiveResult(result.code, result.stderr);
|
|
142
|
+
}
|
|
143
|
+
const report = parseJsonReport(raw);
|
|
144
|
+
if (report === null) {
|
|
145
|
+
return inconclusiveResult(result.code, `unparseable JSON report\n${result.stderr}`);
|
|
146
|
+
}
|
|
147
|
+
const stats = report.stats ?? {};
|
|
148
|
+
const passed = stats.expected ?? 0;
|
|
149
|
+
const failed = stats.unexpected ?? 0;
|
|
150
|
+
const flaky = stats.flaky ?? 0;
|
|
151
|
+
return {
|
|
152
|
+
ok: failed === 0 && passed + flaky > 0,
|
|
153
|
+
passed,
|
|
154
|
+
failed,
|
|
155
|
+
flaky,
|
|
156
|
+
skipped: stats.skipped ?? 0,
|
|
157
|
+
durationMs: stats.duration ?? 0,
|
|
158
|
+
exitCode: result.code,
|
|
159
|
+
// A run that executed nothing tells us nothing — usually a grep that
|
|
160
|
+
// matched no test, which is a caller mistake worth surfacing.
|
|
161
|
+
inconclusive: passed + failed + flaky === 0,
|
|
162
|
+
stderr: result.stderr,
|
|
163
|
+
specs: collectSpecs(report.suites),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
await rm(dir, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"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"}
|
package/dist/steps.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the page-object call trail from Playwright's step tree.
|
|
3
|
+
*
|
|
4
|
+
* This is what turns "the selector [data-testid=gym-card-name] did not resolve"
|
|
5
|
+
* into "gymsPage.expectCardData({ name: 'Blackwater Valley BJJ' }) failed".
|
|
6
|
+
* The first says where the test looked; the second says what it WANTED — which
|
|
7
|
+
* is the input the healing engine actually reasons over.
|
|
8
|
+
*
|
|
9
|
+
* The trail arrives for free when page objects are bound through a wrapper
|
|
10
|
+
* that calls `test.step()`. No spec changes, no extra reporting channel.
|
|
11
|
+
*/
|
|
12
|
+
import type { StepRecord } from '@aplaytest/core';
|
|
13
|
+
/** Structural subset of Playwright's TestStep — keeps this unit-testable. */
|
|
14
|
+
export interface StepLike {
|
|
15
|
+
readonly title: string;
|
|
16
|
+
readonly category: string;
|
|
17
|
+
readonly duration: number;
|
|
18
|
+
readonly startTime: Date;
|
|
19
|
+
readonly error?: unknown;
|
|
20
|
+
readonly steps: readonly StepLike[];
|
|
21
|
+
}
|
|
22
|
+
export interface ParsedStepTitle {
|
|
23
|
+
readonly pageObject: string;
|
|
24
|
+
readonly method: string;
|
|
25
|
+
readonly argsPreview: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function parseStepTitle(title: string): ParsedStepTitle | null;
|
|
28
|
+
/**
|
|
29
|
+
* Domain string arguments from a step title — the accessible names, search
|
|
30
|
+
* terms, and labels the test was looking for. Tier-0 candidate generation
|
|
31
|
+
* matches these against the page's ARIA tree, which is why a page object that
|
|
32
|
+
* takes domain values rather than selectors pays off here.
|
|
33
|
+
*/
|
|
34
|
+
export declare function domainStringArgs(argsPreview: string): string[];
|
|
35
|
+
/** Flatten the step tree into an ordered trail of user-authored steps. */
|
|
36
|
+
export declare function extractSteps(steps: readonly StepLike[]): StepRecord[];
|
|
37
|
+
/**
|
|
38
|
+
* The DEEPEST failing step, not the first.
|
|
39
|
+
*
|
|
40
|
+
* Playwright marks every ancestor of a failure as failed too, so the outermost
|
|
41
|
+
* failed step is usually the whole test body — useless. The innermost one is
|
|
42
|
+
* the actual call that broke.
|
|
43
|
+
*/
|
|
44
|
+
export declare function findFailingStep(steps: readonly StepLike[]): StepRecord | null;
|
|
45
|
+
//# sourceMappingURL=steps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,6EAA6E;AAC7E,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CACrC;AAKD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAMpE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAa9D;AAUD,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,UAAU,EAAE,CAsBrE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,UAAU,GAAG,IAAI,CA4B7E"}
|
package/dist/steps.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the page-object call trail from Playwright's step tree.
|
|
3
|
+
*
|
|
4
|
+
* This is what turns "the selector [data-testid=gym-card-name] did not resolve"
|
|
5
|
+
* into "gymsPage.expectCardData({ name: 'Blackwater Valley BJJ' }) failed".
|
|
6
|
+
* The first says where the test looked; the second says what it WANTED — which
|
|
7
|
+
* is the input the healing engine actually reasons over.
|
|
8
|
+
*
|
|
9
|
+
* The trail arrives for free when page objects are bound through a wrapper
|
|
10
|
+
* that calls `test.step()`. No spec changes, no extra reporting channel.
|
|
11
|
+
*/
|
|
12
|
+
/** `gymsPage.expectCardData({ name: 'X' })` → object / method / args preview. */
|
|
13
|
+
const CALL_PATTERN = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)\((.*)\)$/s;
|
|
14
|
+
export function parseStepTitle(title) {
|
|
15
|
+
const match = CALL_PATTERN.exec(title.trim());
|
|
16
|
+
if (match === null)
|
|
17
|
+
return null;
|
|
18
|
+
const [, pageObject, method, argsPreview] = match;
|
|
19
|
+
if (pageObject === undefined || method === undefined)
|
|
20
|
+
return null;
|
|
21
|
+
return { pageObject, method, argsPreview: argsPreview ?? '' };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Domain string arguments from a step title — the accessible names, search
|
|
25
|
+
* terms, and labels the test was looking for. Tier-0 candidate generation
|
|
26
|
+
* matches these against the page's ARIA tree, which is why a page object that
|
|
27
|
+
* takes domain values rather than selectors pays off here.
|
|
28
|
+
*/
|
|
29
|
+
export function domainStringArgs(argsPreview) {
|
|
30
|
+
const out = [];
|
|
31
|
+
// Match any quote-delimited run, THEN filter. A length constraint inside the
|
|
32
|
+
// pattern lets the engine pair a closing quote with the next opening one —
|
|
33
|
+
// `'a', 'Cork'` would yield the separator `", "` as an "argument", quietly
|
|
34
|
+
// feeding junk into candidate matching.
|
|
35
|
+
const literal = /'([^']*)'|"([^"]*)"/g;
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = literal.exec(argsPreview)) !== null) {
|
|
38
|
+
const value = match[1] ?? match[2];
|
|
39
|
+
if (value !== undefined && isDomainValue(value))
|
|
40
|
+
out.push(value);
|
|
41
|
+
}
|
|
42
|
+
return [...new Set(out)];
|
|
43
|
+
}
|
|
44
|
+
/** Two or more characters, containing at least one letter or digit. */
|
|
45
|
+
function isDomainValue(value) {
|
|
46
|
+
const trimmed = value.trim();
|
|
47
|
+
return trimmed.length >= 2 && /[\p{L}\p{N}]/u.test(trimmed);
|
|
48
|
+
}
|
|
49
|
+
const USER_STEP_CATEGORIES = new Set(['test.step']);
|
|
50
|
+
/** Flatten the step tree into an ordered trail of user-authored steps. */
|
|
51
|
+
export function extractSteps(steps) {
|
|
52
|
+
const out = [];
|
|
53
|
+
const walk = (nodes) => {
|
|
54
|
+
for (const node of nodes) {
|
|
55
|
+
if (USER_STEP_CATEGORIES.has(node.category)) {
|
|
56
|
+
const parsed = parseStepTitle(node.title);
|
|
57
|
+
out.push({
|
|
58
|
+
pageObject: parsed?.pageObject ?? '(unknown)',
|
|
59
|
+
method: parsed?.method ?? node.title,
|
|
60
|
+
args: parsed === null ? [] : domainStringArgs(parsed.argsPreview),
|
|
61
|
+
startedAt: node.startTime.toISOString(),
|
|
62
|
+
durationMs: node.duration,
|
|
63
|
+
failed: node.error !== undefined && node.error !== null,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
walk(node.steps);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(steps);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The DEEPEST failing step, not the first.
|
|
74
|
+
*
|
|
75
|
+
* Playwright marks every ancestor of a failure as failed too, so the outermost
|
|
76
|
+
* failed step is usually the whole test body — useless. The innermost one is
|
|
77
|
+
* the actual call that broke.
|
|
78
|
+
*/
|
|
79
|
+
export function findFailingStep(steps) {
|
|
80
|
+
let deepest = null;
|
|
81
|
+
const walk = (nodes, depth) => {
|
|
82
|
+
for (const node of nodes) {
|
|
83
|
+
const failed = node.error !== undefined && node.error !== null;
|
|
84
|
+
if (failed && USER_STEP_CATEGORIES.has(node.category)) {
|
|
85
|
+
const parsed = parseStepTitle(node.title);
|
|
86
|
+
if (deepest === null || depth > deepest.depth) {
|
|
87
|
+
deepest = {
|
|
88
|
+
depth,
|
|
89
|
+
record: {
|
|
90
|
+
pageObject: parsed?.pageObject ?? '(unknown)',
|
|
91
|
+
method: parsed?.method ?? node.title,
|
|
92
|
+
args: parsed === null ? [] : domainStringArgs(parsed.argsPreview),
|
|
93
|
+
startedAt: node.startTime.toISOString(),
|
|
94
|
+
durationMs: node.duration,
|
|
95
|
+
failed: true,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
walk(node.steps, depth + 1);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
walk(steps, 0);
|
|
104
|
+
return deepest === null ? null : deepest.record;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
|