@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.
Files changed (58) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/api-fixtures.d.ts +71 -0
  3. package/dist/api-fixtures.d.ts.map +1 -0
  4. package/dist/api-fixtures.js +162 -0
  5. package/dist/api-fixtures.js.map +1 -0
  6. package/dist/assemble.d.ts +86 -0
  7. package/dist/assemble.d.ts.map +1 -0
  8. package/dist/assemble.js +176 -0
  9. package/dist/assemble.js.map +1 -0
  10. package/dist/bind.d.ts +43 -0
  11. package/dist/bind.d.ts.map +1 -0
  12. package/dist/bind.js +112 -0
  13. package/dist/bind.js.map +1 -0
  14. package/dist/fixtures.d.ts +54 -0
  15. package/dist/fixtures.d.ts.map +1 -0
  16. package/dist/fixtures.js +210 -0
  17. package/dist/fixtures.js.map +1 -0
  18. package/dist/index.d.ts +31 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +24 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/reporter.d.ts +79 -0
  23. package/dist/reporter.d.ts.map +1 -0
  24. package/dist/reporter.js +277 -0
  25. package/dist/reporter.js.map +1 -0
  26. package/dist/sidecar.d.ts +134 -0
  27. package/dist/sidecar.d.ts.map +1 -0
  28. package/dist/sidecar.js +111 -0
  29. package/dist/sidecar.js.map +1 -0
  30. package/dist/spawn.d.ts +57 -0
  31. package/dist/spawn.d.ts.map +1 -0
  32. package/dist/spawn.js +170 -0
  33. package/dist/spawn.js.map +1 -0
  34. package/dist/steps.d.ts +45 -0
  35. package/dist/steps.d.ts.map +1 -0
  36. package/dist/steps.js +106 -0
  37. package/dist/steps.js.map +1 -0
  38. package/dist-cjs/.tsbuildinfo +1 -0
  39. package/dist-cjs/api-fixtures.js +166 -0
  40. package/dist-cjs/api-fixtures.js.map +1 -0
  41. package/dist-cjs/assemble.js +181 -0
  42. package/dist-cjs/assemble.js.map +1 -0
  43. package/dist-cjs/bind.js +117 -0
  44. package/dist-cjs/bind.js.map +1 -0
  45. package/dist-cjs/fixtures.js +215 -0
  46. package/dist-cjs/fixtures.js.map +1 -0
  47. package/dist-cjs/index.js +64 -0
  48. package/dist-cjs/index.js.map +1 -0
  49. package/dist-cjs/package.json +3 -0
  50. package/dist-cjs/reporter.js +282 -0
  51. package/dist-cjs/reporter.js.map +1 -0
  52. package/dist-cjs/sidecar.js +116 -0
  53. package/dist-cjs/sidecar.js.map +1 -0
  54. package/dist-cjs/spawn.js +174 -0
  55. package/dist-cjs/spawn.js.map +1 -0
  56. package/dist-cjs/steps.js +112 -0
  57. package/dist-cjs/steps.js.map +1 -0
  58. package/package.json +51 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Assemble an EvidenceBundle from Playwright's test objects plus the optional
3
+ * fixture sidecars.
4
+ *
5
+ * Deliberately PURE and synchronous, over structural interfaces rather than
6
+ * Playwright's own types: the whole thing is unit-testable with plain object
7
+ * fixtures, no browser and no runner. The reporter does the async I/O and
8
+ * hands the results in.
9
+ */
10
+ import { classify, evidenceId, joinErrors, parseLocator, parsePlaywrightError, testIdDistance, } from '@aplaytest/core';
11
+ import { extractSteps, findFailingStep } from './steps.js';
12
+ const EMPTY_SIDECARS = { page: null, network: null, console: null, intent: null };
13
+ function attachmentPath(result, name) {
14
+ return result.attachments.find(a => a.name === name)?.path ?? null;
15
+ }
16
+ /** Candidates further than this from the intended id are not plausible renames. */
17
+ const MAX_SEED_DISTANCE = 0.4;
18
+ /** Enough for a ranker to choose from; small enough to stay readable. */
19
+ const MAX_SEED_CANDIDATES = 10;
20
+ /**
21
+ * Seed candidates from the test-id index.
22
+ *
23
+ * Two things this must NOT do. It must not return every id on the page — a
24
+ * real page carries dozens, and an unranked dump is noise that pushes the
25
+ * cost of choosing onto whatever reads it. And it must not claim the
26
+ * candidates resolve uniquely: no browser is available in the reporter, so
27
+ * `matchCount` is -1 to mean "not checked" rather than asserting a uniqueness
28
+ * nobody measured. The heal engine verifies against a live page before it
29
+ * ranks anything.
30
+ */
31
+ function seedCandidates(intended, present) {
32
+ const parsed = parseLocator(intended);
33
+ if (parsed === null || parsed.strategy !== 'testid')
34
+ return [];
35
+ return present
36
+ .filter(id => id !== parsed.value)
37
+ .map(id => ({ id, distance: testIdDistance(parsed.value, id) }))
38
+ .filter(({ distance }) => distance <= MAX_SEED_DISTANCE)
39
+ .sort((a, b) => a.distance - b.distance)
40
+ .slice(0, MAX_SEED_CANDIDATES)
41
+ .map(({ id, distance }) => ({
42
+ strategy: 'testid',
43
+ expression: `getByTestId('${id}')`,
44
+ matchCount: -1,
45
+ visible: false,
46
+ enabled: false,
47
+ accessibleName: null,
48
+ boundingBox: null,
49
+ semanticDistance: distance,
50
+ stabilityRank: 0,
51
+ }));
52
+ }
53
+ export function toClassifiable(result, sidecars, timeoutMs) {
54
+ const { message, stack } = joinErrors(result.errors);
55
+ const parsed = parsePlaywrightError(message, stack);
56
+ return {
57
+ message,
58
+ stack,
59
+ matcher: parsed.matcher,
60
+ timedOut: parsed.timedOut || result.status === 'timedOut',
61
+ consoleErrors: sidecars.console?.errors ?? [],
62
+ failedRequests: (sidecars.network?.failed ?? []),
63
+ budgetUsedRatio: timeoutMs > 0 ? result.duration / timeoutMs : 0,
64
+ };
65
+ }
66
+ /**
67
+ * Assemble an {@link EvidenceBundle} from a Playwright test result.
68
+ *
69
+ * The reporter calls this once per failure. Classification, step extraction,
70
+ * and sidecar merge happen here so a reporting failure cannot become a test
71
+ * failure — the caller swallows errors from this function.
72
+ *
73
+ * @param input - Playwright test/result shapes, run context, and optional
74
+ * capture-fixture sidecars (ARIA, network, console, intent).
75
+ * @returns A schema-versioned evidence bundle ready to persist.
76
+ */
77
+ export function assembleBundle(input) {
78
+ const { test, result, context } = input;
79
+ const sidecars = { ...EMPTY_SIDECARS, ...input.sidecars };
80
+ const { message, stack } = joinErrors(result.errors);
81
+ const parsed = parsePlaywrightError(message, stack);
82
+ const classifiable = toClassifiable(result, sidecars, context.timeoutMs);
83
+ const classification = classify(classifiable);
84
+ const steps = extractSteps(result.steps);
85
+ const failingStep = findFailingStep(result.steps);
86
+ // The fixture's recorded selector is authoritative when present — it knows
87
+ // which locator the page object actually built. Falling back to the parsed
88
+ // error text is a best effort for suites without the fixture wrapper.
89
+ const selector = sidecars.intent?.selector ?? parsed.locator;
90
+ return {
91
+ schemaVersion: 1,
92
+ id: evidenceId({
93
+ runId: context.runId,
94
+ testId: test.id,
95
+ project: context.project,
96
+ retry: result.retry,
97
+ }),
98
+ runId: context.runId,
99
+ traceId: context.traceId ?? '',
100
+ capturedAt: new Date().toISOString(),
101
+ test: {
102
+ id: test.id,
103
+ title: test.title,
104
+ titlePath: test.titlePath(),
105
+ file: test.location.file,
106
+ line: test.location.line,
107
+ project: context.project,
108
+ tags: test.tags,
109
+ retry: result.retry,
110
+ workerIndex: result.workerIndex,
111
+ shard: context.shard,
112
+ },
113
+ failure: {
114
+ kind: classification.kind,
115
+ message,
116
+ stack,
117
+ matcher: parsed.matcher,
118
+ expected: parsed.expected,
119
+ actual: parsed.actual,
120
+ timedOut: classifiable.timedOut,
121
+ },
122
+ intent: {
123
+ steps,
124
+ failingStep,
125
+ selector,
126
+ selectorSource: sidecars.intent?.selectorSource ?? null,
127
+ },
128
+ page: {
129
+ url: sidecars.page?.url ?? '',
130
+ title: sidecars.page?.title ?? '',
131
+ ariaSnapshot: sidecars.page?.ariaSnapshot ?? '',
132
+ candidates: seedCandidates(selector, sidecars.page?.testIdsPresent ?? []),
133
+ htmlDigest: sidecars.page?.htmlDigest ?? null,
134
+ testIdsPresent: sidecars.page?.testIdsPresent ?? [],
135
+ },
136
+ visual: {
137
+ screenshotPath: attachmentPath(result, 'screenshot'),
138
+ diffPath: attachmentPath(result, 'diff'),
139
+ diffPixelRatio: null,
140
+ },
141
+ network: {
142
+ failed: (sidecars.network?.failed ?? []),
143
+ slow: (sidecars.network?.slow ?? []),
144
+ statusCounts: sidecars.network?.statusCounts ?? {},
145
+ },
146
+ console: {
147
+ errors: sidecars.console?.errors ?? [],
148
+ warnings: sidecars.console?.warnings ?? [],
149
+ },
150
+ timing: {
151
+ testMs: result.duration,
152
+ failingActionMs: failingStep?.durationMs ?? null,
153
+ navigationMs: null,
154
+ budgetUsedRatio: classifiable.budgetUsedRatio,
155
+ },
156
+ env: {
157
+ appEnv: context.appEnv,
158
+ baseUrl: context.baseUrl,
159
+ browser: context.browser,
160
+ platform: context.platform,
161
+ workers: context.workers,
162
+ commit: context.commit,
163
+ changedPaths: context.changedPaths,
164
+ },
165
+ appSpans: null,
166
+ artifacts: {
167
+ tracePath: attachmentPath(result, 'trace'),
168
+ videoPath: attachmentPath(result, 'video'),
169
+ },
170
+ };
171
+ }
172
+ /** Re-exported so the reporter can record the rule that fired. */
173
+ export function classifyResult(result, sidecars, timeoutMs) {
174
+ return classify(toClassifiable(result, sidecars, timeoutMs));
175
+ }
176
+ //# sourceMappingURL=assemble.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assemble.js","sourceRoot":"","sources":["../src/assemble.ts"],"names":[],"mappings":"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"}
package/dist/bind.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Page-object binding with step instrumentation.
3
+ *
4
+ * A drop-in for the common `bindPage(module, page)` helper: it binds each
5
+ * exported function's first `page` argument, and additionally wraps the call
6
+ * in `test.step()` so the call trail reaches the reporter.
7
+ *
8
+ * That trail is the difference between a failure that says
9
+ *
10
+ * locator getByTestId('gym-card-name') did not resolve
11
+ *
12
+ * and one that says
13
+ *
14
+ * gymsPage.expectCardData({ name: 'Blackwater Valley BJJ' }) failed
15
+ *
16
+ * The first states where the test looked; the second states what it WANTED,
17
+ * and the domain values in it are what candidate generation matches against
18
+ * the page's accessibility tree.
19
+ */
20
+ import type { Page } from '@playwright/test';
21
+ export type BoundPageObject<T> = {
22
+ [K in keyof T]: T[K] extends (page: Page, ...args: infer A) => infer R ? (...args: A) => R : T[K];
23
+ };
24
+ /**
25
+ * Render arguments the way a developer would have written them —
26
+ * `{ name: 'Blackwater Valley BJJ' }`, not `{"name":"Blackwater Valley BJJ"}`.
27
+ *
28
+ * The distinction is load-bearing, not cosmetic: domain-value extraction pulls
29
+ * quoted literals out of this string, and JSON's quoted KEYS would arrive
30
+ * looking exactly like values. `name` would then be matched against the ARIA
31
+ * tree alongside the gym it labels.
32
+ */
33
+ export declare function previewValue(value: unknown, depth?: number): string;
34
+ export declare function previewArgs(args: readonly unknown[]): string;
35
+ /**
36
+ * Bind a page-object module to a page.
37
+ *
38
+ * `name` is what appears before the method in the step title, and should match
39
+ * the fixture name the specs use (`gymsPage`) so a reader sees the same
40
+ * vocabulary in the report as in the test.
41
+ */
42
+ export declare function bindPage<T extends object>(mod: T, page: Page, name?: string): BoundPageObject<T>;
43
+ //# sourceMappingURL=bind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;KAC9B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAClG,CAAC;AAiBF;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,MAAM,CAmC9D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAK5D;AAMD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,CAyBhG"}
package/dist/bind.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Page-object binding with step instrumentation.
3
+ *
4
+ * A drop-in for the common `bindPage(module, page)` helper: it binds each
5
+ * exported function's first `page` argument, and additionally wraps the call
6
+ * in `test.step()` so the call trail reaches the reporter.
7
+ *
8
+ * That trail is the difference between a failure that says
9
+ *
10
+ * locator getByTestId('gym-card-name') did not resolve
11
+ *
12
+ * and one that says
13
+ *
14
+ * gymsPage.expectCardData({ name: 'Blackwater Valley BJJ' }) failed
15
+ *
16
+ * The first states where the test looked; the second states what it WANTED,
17
+ * and the domain values in it are what candidate generation matches against
18
+ * the page's accessibility tree.
19
+ */
20
+ import { test } from '@playwright/test';
21
+ /** Values whose contents must never reach a step title. */
22
+ const SENSITIVE_KEYS = ['password', 'token', 'secret', 'authorization', 'apikey', 'api_key'];
23
+ const MAX_PREVIEW_LENGTH = 120;
24
+ const MAX_DEPTH = 2;
25
+ function isIdentifier(key) {
26
+ return /^[A-Za-z_$][\w$]*$/.test(key);
27
+ }
28
+ function isSensitive(key) {
29
+ const normalised = key.toLowerCase().replace(/[-_]/g, '');
30
+ return SENSITIVE_KEYS.some(k => normalised.includes(k.replace(/[-_]/g, '')));
31
+ }
32
+ /**
33
+ * Render arguments the way a developer would have written them —
34
+ * `{ name: 'Blackwater Valley BJJ' }`, not `{"name":"Blackwater Valley BJJ"}`.
35
+ *
36
+ * The distinction is load-bearing, not cosmetic: domain-value extraction pulls
37
+ * quoted literals out of this string, and JSON's quoted KEYS would arrive
38
+ * looking exactly like values. `name` would then be matched against the ARIA
39
+ * tree alongside the gym it labels.
40
+ */
41
+ export function previewValue(value, depth = 0) {
42
+ if (value === null)
43
+ return 'null';
44
+ if (value === undefined)
45
+ return 'undefined';
46
+ switch (typeof value) {
47
+ case 'string':
48
+ return `'${value.replace(/'/g, "\\'")}'`;
49
+ case 'number':
50
+ case 'boolean':
51
+ case 'bigint':
52
+ return String(value);
53
+ case 'function':
54
+ return 'fn';
55
+ case 'symbol':
56
+ return value.toString();
57
+ default:
58
+ break;
59
+ }
60
+ if (depth >= MAX_DEPTH)
61
+ return Array.isArray(value) ? '[…]' : '{…}';
62
+ if (Array.isArray(value)) {
63
+ return `[${value.map(v => previewValue(v, depth + 1)).join(', ')}]`;
64
+ }
65
+ if (value instanceof RegExp)
66
+ return value.toString();
67
+ if (value instanceof Date)
68
+ return value.toISOString();
69
+ const entries = Object.entries(value).map(([key, inner]) => {
70
+ const renderedKey = isIdentifier(key) ? key : `'${key}'`;
71
+ const renderedValue = isSensitive(key) ? "'[redacted]'" : previewValue(inner, depth + 1);
72
+ return `${renderedKey}: ${renderedValue}`;
73
+ });
74
+ return entries.length === 0 ? '{}' : `{ ${entries.join(', ')} }`;
75
+ }
76
+ export function previewArgs(args) {
77
+ const rendered = args.map(a => previewValue(a)).join(', ');
78
+ return rendered.length > MAX_PREVIEW_LENGTH
79
+ ? `${rendered.slice(0, MAX_PREVIEW_LENGTH - 1)}…`
80
+ : rendered;
81
+ }
82
+ function isAsyncFunction(fn) {
83
+ return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction';
84
+ }
85
+ /**
86
+ * Bind a page-object module to a page.
87
+ *
88
+ * `name` is what appears before the method in the step title, and should match
89
+ * the fixture name the specs use (`gymsPage`) so a reader sees the same
90
+ * vocabulary in the report as in the test.
91
+ */
92
+ export function bindPage(mod, page, name) {
93
+ const bound = {};
94
+ for (const [key, value] of Object.entries(mod)) {
95
+ if (typeof value !== 'function') {
96
+ bound[key] = value;
97
+ continue;
98
+ }
99
+ const fn = value;
100
+ // Only async functions are wrapped. `test.step` always returns a promise,
101
+ // so wrapping a synchronous helper would silently change its contract and
102
+ // make the mapped return type a lie.
103
+ if (!isAsyncFunction(fn)) {
104
+ bound[key] = (...args) => fn(page, ...args);
105
+ continue;
106
+ }
107
+ const label = name === undefined ? key : `${name}.${key}`;
108
+ bound[key] = (...args) => test.step(`${label}(${previewArgs(args)})`, () => fn(page, ...args));
109
+ }
110
+ return bound;
111
+ }
112
+ //# sourceMappingURL=bind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.js","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Capture fixtures — the optional upgrade over the reporter alone.
3
+ *
4
+ * A Playwright reporter runs in the main process and cannot reach into the
5
+ * browser, so the things that best explain a failure — the ARIA tree, which
6
+ * test ids actually exist, what the network did, what the app logged — have to
7
+ * be collected in the worker and handed over as attachments.
8
+ *
9
+ * Three properties this file must hold, in priority order:
10
+ *
11
+ * 1. NEVER fail a test. Every capture is wrapped; a capture error is a
12
+ * warning on stderr, never an exception. Diagnostics that can break the
13
+ * suite are worse than no diagnostics.
14
+ * 2. Cost nothing on green. The expensive work (ARIA snapshot, test-id
15
+ * sweep) runs only when a test has actually failed.
16
+ * 3. Need no spec changes. Registered with `auto: true`, so adding it is a
17
+ * one-line change in the fixture composition and nothing else.
18
+ *
19
+ * UI PROJECTS ONLY. `auto: true` plus a `{ page }` dependency means Playwright
20
+ * launches a browser for every test in the project, including tests that only
21
+ * use `request`. API projects must compose `atestApiFixtures` from
22
+ * ./api-fixtures.js instead — see the note there.
23
+ */
24
+ import { type Page, type TestInfo } from '@playwright/test';
25
+ export interface CaptureOptions {
26
+ /** Attribute used for test ids. Mirrors Playwright's `testIdAttribute`. */
27
+ readonly testIdAttribute?: string;
28
+ /** Requests slower than this are recorded as slow. */
29
+ readonly slowRequestMs?: number;
30
+ /** Hard caps, so a chatty page cannot exhaust worker memory. */
31
+ readonly maxRequests?: number;
32
+ readonly maxConsoleEntries?: number;
33
+ }
34
+ export declare function createCaptureFixture(options?: CaptureOptions): ({ page }: {
35
+ page: Page;
36
+ }, use: (value: void) => Promise<void>, testInfo: TestInfo) => Promise<void>;
37
+ /**
38
+ * Spread into a fixture composition:
39
+ *
40
+ * export const test = base.extend({ ...atestFixtures, ...featureFixtures });
41
+ *
42
+ * `auto: true` means specs never mention it.
43
+ */
44
+ export declare const atestFixtures: {
45
+ atestCapture: readonly [({ page }: {
46
+ page: Page;
47
+ }, use: (value: void) => Promise<void>, testInfo: TestInfo) => Promise<void>, {
48
+ readonly auto: true;
49
+ }];
50
+ };
51
+ /** Ready-made `test` for suites with no fixtures of their own. */
52
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
53
+ export { expect } from '@playwright/test';
54
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAgB,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI1E,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,sDAAsD;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,gEAAgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAiED,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,cAAmB,IAI7D,UAAU;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,EACxB,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,EACnC,UAAU,QAAQ,KACjB,OAAO,CAAC,IAAI,CAAC,CAkJjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;uCA9JZ;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,OACnB,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,YACzB,QAAQ,KACjB,OAAO,CAAC,IAAI,CAAC;;;CA6JjB,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,IAAI,6OAAoC,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Capture fixtures — the optional upgrade over the reporter alone.
3
+ *
4
+ * A Playwright reporter runs in the main process and cannot reach into the
5
+ * browser, so the things that best explain a failure — the ARIA tree, which
6
+ * test ids actually exist, what the network did, what the app logged — have to
7
+ * be collected in the worker and handed over as attachments.
8
+ *
9
+ * Three properties this file must hold, in priority order:
10
+ *
11
+ * 1. NEVER fail a test. Every capture is wrapped; a capture error is a
12
+ * warning on stderr, never an exception. Diagnostics that can break the
13
+ * suite are worse than no diagnostics.
14
+ * 2. Cost nothing on green. The expensive work (ARIA snapshot, test-id
15
+ * sweep) runs only when a test has actually failed.
16
+ * 3. Need no spec changes. Registered with `auto: true`, so adding it is a
17
+ * one-line change in the fixture composition and nothing else.
18
+ *
19
+ * UI PROJECTS ONLY. `auto: true` plus a `{ page }` dependency means Playwright
20
+ * launches a browser for every test in the project, including tests that only
21
+ * use `request`. API projects must compose `atestApiFixtures` from
22
+ * ./api-fixtures.js instead — see the note there.
23
+ */
24
+ import { test as base } from '@playwright/test';
25
+ import { SIDECAR } from './sidecar.js';
26
+ const DEFAULTS = {
27
+ testIdAttribute: 'data-testid',
28
+ slowRequestMs: 2_000,
29
+ maxRequests: 200,
30
+ maxConsoleEntries: 100,
31
+ };
32
+ function warn(message) {
33
+ process.stderr.write(`[atest] ${message}\n`);
34
+ }
35
+ /** Run a capture step; never let it escape. */
36
+ async function safely(label, fn, fallback) {
37
+ try {
38
+ return await fn();
39
+ }
40
+ catch (error) {
41
+ warn(`capture "${label}" failed: ${error instanceof Error ? error.message : String(error)}`);
42
+ return fallback;
43
+ }
44
+ }
45
+ async function attach(testInfo, name, payload) {
46
+ await safely(`attach ${name}`, async () => {
47
+ await testInfo.attach(name, {
48
+ body: JSON.stringify(payload),
49
+ contentType: 'application/json',
50
+ });
51
+ }, undefined);
52
+ }
53
+ /**
54
+ * Collect every test id currently in the DOM.
55
+ *
56
+ * This single list answers the most common healing question — "was the id
57
+ * renamed, or is the element genuinely gone?" — without a model and without a
58
+ * second browser session.
59
+ */
60
+ async function collectTestIds(page, attribute) {
61
+ const ids = await page.evaluate(attr => {
62
+ // Array.from rather than for-of: iterating a NodeList needs the
63
+ // DOM.Iterable lib, and this package should carry the smallest browser
64
+ // surface that compiles.
65
+ const values = Array.from(document.querySelectorAll(`[${attr}]`), element => element.getAttribute(attr));
66
+ return values.filter((value) => value !== null && value !== '');
67
+ }, attribute);
68
+ return [...new Set(ids)].sort();
69
+ }
70
+ export function createCaptureFixture(options = {}) {
71
+ const config = { ...DEFAULTS, ...options };
72
+ return async ({ page }, use, testInfo) => {
73
+ const requests = [];
74
+ const consoleErrors = [];
75
+ const consoleWarnings = [];
76
+ const started = new Map();
77
+ const routes = new Set();
78
+ /**
79
+ * Route coverage is recorded for EVERY test, passing or failing — unlike
80
+ * the failure evidence below. It costs one listener and a Set, and it is
81
+ * what lets impact analysis narrow past a shared fixture barrel that
82
+ * makes every spec look like it depends on every feature.
83
+ */
84
+ const onNavigated = (frame) => {
85
+ try {
86
+ const { pathname } = new URL(frame.url());
87
+ // Path only: query strings are per-test data, not coverage.
88
+ if (pathname !== '' && pathname !== 'blank')
89
+ routes.add(pathname);
90
+ }
91
+ catch {
92
+ // about:blank and data: URLs are not routes.
93
+ }
94
+ };
95
+ const onRequest = (request) => {
96
+ started.set(request.url(), Date.now());
97
+ };
98
+ const record = (entry) => {
99
+ if (requests.length < config.maxRequests)
100
+ requests.push(entry);
101
+ };
102
+ const onResponse = (response) => {
103
+ const url = response.url();
104
+ const startedAt = started.get(url);
105
+ record({
106
+ url,
107
+ method: response.request().method(),
108
+ status: response.status(),
109
+ durationMs: startedAt === undefined ? 0 : Date.now() - startedAt,
110
+ failureText: null,
111
+ schemaError: null,
112
+ });
113
+ };
114
+ const onRequestFailed = (request) => {
115
+ const url = request.url();
116
+ const startedAt = started.get(url);
117
+ record({
118
+ url,
119
+ method: request.method(),
120
+ status: null,
121
+ durationMs: startedAt === undefined ? 0 : Date.now() - startedAt,
122
+ failureText: request.failure()?.errorText ?? 'request failed',
123
+ schemaError: null,
124
+ });
125
+ };
126
+ const onConsole = (message) => {
127
+ const type = message.type();
128
+ if (type === 'error' && consoleErrors.length < config.maxConsoleEntries) {
129
+ consoleErrors.push(message.text());
130
+ }
131
+ else if (type === 'warning' && consoleWarnings.length < config.maxConsoleEntries) {
132
+ consoleWarnings.push(message.text());
133
+ }
134
+ };
135
+ /**
136
+ * An uncaught exception is the single strongest signal that the APP broke
137
+ * rather than the test. It is prefixed to match the classifier's uncaught
138
+ * patterns, which is what routes the failure to `app_error` — a kind that
139
+ * is never healed.
140
+ */
141
+ const onPageError = (error) => {
142
+ if (consoleErrors.length < config.maxConsoleEntries) {
143
+ consoleErrors.push(`Uncaught ${error.name}: ${error.message}`);
144
+ }
145
+ };
146
+ page.on('framenavigated', onNavigated);
147
+ page.on('request', onRequest);
148
+ page.on('response', onResponse);
149
+ page.on('requestfailed', onRequestFailed);
150
+ page.on('console', onConsole);
151
+ page.on('pageerror', onPageError);
152
+ await use();
153
+ page.off('framenavigated', onNavigated);
154
+ page.off('request', onRequest);
155
+ page.off('response', onResponse);
156
+ page.off('requestfailed', onRequestFailed);
157
+ page.off('console', onConsole);
158
+ page.off('pageerror', onPageError);
159
+ // Coverage is attached regardless of outcome — a passing test's routes are
160
+ // exactly what future selection needs to know about.
161
+ if (routes.size > 0) {
162
+ await attach(testInfo, SIDECAR.coverage, { routes: [...routes].sort() });
163
+ }
164
+ // Everything below is failure-only: green tests pay nothing beyond the
165
+ // listeners above.
166
+ if (testInfo.status === testInfo.expectedStatus)
167
+ return;
168
+ const pageClosed = page.isClosed();
169
+ const ariaSnapshot = pageClosed
170
+ ? ''
171
+ : await safely('aria snapshot', () => page.locator('body').ariaSnapshot(), '');
172
+ const testIdsPresent = pageClosed
173
+ ? []
174
+ : await safely('test-id sweep', () => collectTestIds(page, config.testIdAttribute), []);
175
+ const url = pageClosed ? '' : await safely('url', async () => page.url(), '');
176
+ const title = pageClosed ? '' : await safely('title', () => page.title(), '');
177
+ await attach(testInfo, SIDECAR.page, {
178
+ url,
179
+ title,
180
+ ariaSnapshot,
181
+ testIdsPresent,
182
+ htmlDigest: null,
183
+ });
184
+ const failed = requests.filter(r => r.failureText !== null || (r.status ?? 0) >= 400);
185
+ // A fixed threshold, not a per-route p95: that needs history, and
186
+ // claiming one now would be a number nobody measured.
187
+ const slow = requests.filter(r => r.durationMs >= config.slowRequestMs);
188
+ const statusCounts = {};
189
+ for (const request of requests) {
190
+ const key = request.status === null ? 'failed' : String(request.status);
191
+ statusCounts[key] = (statusCounts[key] ?? 0) + 1;
192
+ }
193
+ await attach(testInfo, SIDECAR.network, { failed, slow, statusCounts });
194
+ await attach(testInfo, SIDECAR.console, { errors: consoleErrors, warnings: consoleWarnings });
195
+ };
196
+ }
197
+ /**
198
+ * Spread into a fixture composition:
199
+ *
200
+ * export const test = base.extend({ ...atestFixtures, ...featureFixtures });
201
+ *
202
+ * `auto: true` means specs never mention it.
203
+ */
204
+ export const atestFixtures = {
205
+ atestCapture: [createCaptureFixture(), { auto: true }],
206
+ };
207
+ /** Ready-made `test` for suites with no fixtures of their own. */
208
+ export const test = base.extend({ ...atestFixtures });
209
+ export { expect } from '@playwright/test';
210
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"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"}