@setsquare/review-sdk 0.0.1
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/LICENSE +202 -0
- package/NOTICE +57 -0
- package/README.md +338 -0
- package/THIRD-PARTY-NOTICES.md +61 -0
- package/dist/autoscan.d.ts +25 -0
- package/dist/autoscan.d.ts.map +1 -0
- package/dist/autoscan.js +332 -0
- package/dist/autoscan.js.map +1 -0
- package/dist/axe.d.ts +96 -0
- package/dist/axe.d.ts.map +1 -0
- package/dist/axe.js +210 -0
- package/dist/axe.js.map +1 -0
- package/dist/bin/setsquare.d.ts +3 -0
- package/dist/bin/setsquare.d.ts.map +1 -0
- package/dist/bin/setsquare.js +21 -0
- package/dist/bin/setsquare.js.map +1 -0
- package/dist/brand.d.ts +84 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +335 -0
- package/dist/brand.js.map +1 -0
- package/dist/checks/dynamic.d.ts +57 -0
- package/dist/checks/dynamic.d.ts.map +1 -0
- package/dist/checks/dynamic.js +184 -0
- package/dist/checks/dynamic.js.map +1 -0
- package/dist/checks/rule-pack.d.ts +34 -0
- package/dist/checks/rule-pack.d.ts.map +1 -0
- package/dist/checks/rule-pack.js +270 -0
- package/dist/checks/rule-pack.js.map +1 -0
- package/dist/checks/versions.d.ts +28 -0
- package/dist/checks/versions.d.ts.map +1 -0
- package/dist/checks/versions.js +45 -0
- package/dist/checks/versions.js.map +1 -0
- package/dist/ci.d.ts +37 -0
- package/dist/ci.d.ts.map +1 -0
- package/dist/ci.js +199 -0
- package/dist/ci.js.map +1 -0
- package/dist/contract.d.ts +334 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +35 -0
- package/dist/contract.js.map +1 -0
- package/dist/dedupe.d.ts +79 -0
- package/dist/dedupe.d.ts.map +1 -0
- package/dist/dedupe.js +102 -0
- package/dist/dedupe.js.map +1 -0
- package/dist/document.d.ts +29 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +94 -0
- package/dist/document.js.map +1 -0
- package/dist/envelope.d.ts +124 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +337 -0
- package/dist/envelope.js.map +1 -0
- package/dist/escape.d.ts +14 -0
- package/dist/escape.d.ts.map +1 -0
- package/dist/escape.js +20 -0
- package/dist/escape.js.map +1 -0
- package/dist/findings.d.ts +47 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +229 -0
- package/dist/findings.js.map +1 -0
- package/dist/fixture.d.ts +7 -0
- package/dist/fixture.d.ts.map +1 -0
- package/dist/fixture.js +237 -0
- package/dist/fixture.js.map +1 -0
- package/dist/group.d.ts +214 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +709 -0
- package/dist/group.js.map +1 -0
- package/dist/html-report.d.ts +68 -0
- package/dist/html-report.d.ts.map +1 -0
- package/dist/html-report.js +649 -0
- package/dist/html-report.js.map +1 -0
- package/dist/human.d.ts +65 -0
- package/dist/human.d.ts.map +1 -0
- package/dist/human.js +111 -0
- package/dist/human.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/infer.d.ts +65 -0
- package/dist/infer.d.ts.map +1 -0
- package/dist/infer.js +67 -0
- package/dist/infer.js.map +1 -0
- package/dist/insights.d.ts +140 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +483 -0
- package/dist/insights.js.map +1 -0
- package/dist/install.d.ts +8 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +114 -0
- package/dist/install.js.map +1 -0
- package/dist/links.d.ts +45 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +45 -0
- package/dist/links.js.map +1 -0
- package/dist/mask.d.ts +85 -0
- package/dist/mask.d.ts.map +1 -0
- package/dist/mask.js +98 -0
- package/dist/mask.js.map +1 -0
- package/dist/observe.d.ts +51 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +233 -0
- package/dist/observe.js.map +1 -0
- package/dist/options.d.ts +249 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +185 -0
- package/dist/options.js.map +1 -0
- package/dist/output.d.ts +113 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +231 -0
- package/dist/output.js.map +1 -0
- package/dist/perf.d.ts +11 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/perf.js +150 -0
- package/dist/perf.js.map +1 -0
- package/dist/reading-level.d.ts +73 -0
- package/dist/reading-level.d.ts.map +1 -0
- package/dist/reading-level.js +189 -0
- package/dist/reading-level.js.map +1 -0
- package/dist/reporter.d.ts +94 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +709 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rules.d.ts +55 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +77 -0
- package/dist/rules.js.map +1 -0
- package/dist/runtime.d.ts +32 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +67 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sanitise.d.ts +116 -0
- package/dist/sanitise.d.ts.map +1 -0
- package/dist/sanitise.js +247 -0
- package/dist/sanitise.js.map +1 -0
- package/dist/scan.d.ts +60 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +246 -0
- package/dist/scan.js.map +1 -0
- package/dist/screens.d.ts +98 -0
- package/dist/screens.d.ts.map +1 -0
- package/dist/screens.js +301 -0
- package/dist/screens.js.map +1 -0
- package/dist/settle.d.ts +65 -0
- package/dist/settle.d.ts.map +1 -0
- package/dist/settle.js +162 -0
- package/dist/settle.js.map +1 -0
- package/dist/shots.d.ts +109 -0
- package/dist/shots.d.ts.map +1 -0
- package/dist/shots.js +113 -0
- package/dist/shots.js.map +1 -0
- package/dist/style.d.ts +47 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +154 -0
- package/dist/style.js.map +1 -0
- package/dist/suite.d.ts +218 -0
- package/dist/suite.d.ts.map +1 -0
- package/dist/suite.js +219 -0
- package/dist/suite.js.map +1 -0
- package/dist/summary.d.ts +268 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +1308 -0
- package/dist/summary.js.map +1 -0
- package/dist/unscannable.d.ts +75 -0
- package/dist/unscannable.d.ts.map +1 -0
- package/dist/unscannable.js +111 -0
- package/dist/unscannable.js.map +1 -0
- package/dist/versions.d.ts +13 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +39 -0
- package/dist/versions.js.map +1 -0
- package/dist/wcag-map.d.ts +19 -0
- package/dist/wcag-map.d.ts.map +1 -0
- package/dist/wcag-map.js +52 -0
- package/dist/wcag-map.js.map +1 -0
- package/dist/with-setsquare.d.ts +26 -0
- package/dist/with-setsquare.d.ts.map +1 -0
- package/dist/with-setsquare.js +70 -0
- package/dist/with-setsquare.js.map +1 -0
- package/package.json +81 -0
package/dist/shots.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshots, indexed rather than taken.
|
|
3
|
+
*
|
|
4
|
+
* This module is the whole reason the visual report does not breach decision 4.
|
|
5
|
+
* Setsquare holds no pixels: it takes no screenshot, writes no image, and
|
|
6
|
+
* copies nothing. What it does is read the image attachments Playwright ALREADY
|
|
7
|
+
* wrote — because the customer set `screenshot: 'on'`, called
|
|
8
|
+
* `toHaveScreenshot()`, or attached one themselves — and record where they are.
|
|
9
|
+
* The contact sheet then links to those files where they lie.
|
|
10
|
+
*
|
|
11
|
+
* That distinction is not pedantry. "The local path holds no pixels" is what
|
|
12
|
+
* makes PII-safety structural rather than best-effort, and a helper that
|
|
13
|
+
* screenshotted on the customer's behalf would move the responsibility for an
|
|
14
|
+
* unmasked capture from their config to our code. Indexing leaves it exactly
|
|
15
|
+
* where it already was: with the person who turned Playwright's screenshots on.
|
|
16
|
+
*
|
|
17
|
+
* It also means the SDK cannot compare anything. Where a comparison appears in
|
|
18
|
+
* the report, Playwright's own `toHaveScreenshot()` made it — we found the
|
|
19
|
+
* expected/actual/diff trio it attached and put the three side by side. There is
|
|
20
|
+
* no baseline here, no history, and no verdict: a person scans and decides.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Which part of a comparison an image is, or `shot` for a standalone capture.
|
|
24
|
+
*
|
|
25
|
+
* Read from the attachment name because that is where Playwright puts it:
|
|
26
|
+
* `toHaveScreenshot()` attaches `<base>-expected`, `-actual`, `-diff` and
|
|
27
|
+
* (on a retry that changed) `-previous`, all sharing one base name.
|
|
28
|
+
*/
|
|
29
|
+
export type ShotRole = 'expected' | 'previous' | 'actual' | 'diff' | 'shot';
|
|
30
|
+
export interface Shot {
|
|
31
|
+
/** The attachment name, exactly as Playwright reported it. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Name with the role suffix and extension removed; the trio's shared key. */
|
|
34
|
+
base: string;
|
|
35
|
+
role: ShotRole;
|
|
36
|
+
contentType: string;
|
|
37
|
+
/** Absolute path, on the machine that captured it. Never read, only linked. */
|
|
38
|
+
path: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The attachment shape, structurally typed.
|
|
42
|
+
*
|
|
43
|
+
* Playwright's own `TestResult['attachments']` element, restated as the three
|
|
44
|
+
* fields this module reads. Typed structurally so the classifier can be tested
|
|
45
|
+
* without constructing a Playwright test result, which is a large object with a
|
|
46
|
+
* lot of it irrelevant here.
|
|
47
|
+
*/
|
|
48
|
+
export interface ImageAttachment {
|
|
49
|
+
name: string;
|
|
50
|
+
contentType: string;
|
|
51
|
+
path?: string | undefined;
|
|
52
|
+
body?: Buffer | undefined;
|
|
53
|
+
}
|
|
54
|
+
export interface CollectShotsResult {
|
|
55
|
+
shots: Shot[];
|
|
56
|
+
/** Image attachments that exist only in memory, and so cannot be linked to. */
|
|
57
|
+
inMemory: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pull the image attachments out of one test result.
|
|
61
|
+
*
|
|
62
|
+
* Two cases are deliberately not equivalent. An attachment with a `path` is a
|
|
63
|
+
* file on disk we can link to. An attachment with only a `body` is an image
|
|
64
|
+
* Playwright is holding in memory — `testInfo.attach(name, { body })` never
|
|
65
|
+
* writes it — and putting it in the report would mean writing the bytes
|
|
66
|
+
* ourselves, which is the one thing this module exists not to do. Those are
|
|
67
|
+
* COUNTED and reported as a warning, because an image the reader cannot see
|
|
68
|
+
* must not silently look like an image that was never taken.
|
|
69
|
+
*
|
|
70
|
+
* Selection is by `contentType` rather than by file extension. Playwright always
|
|
71
|
+
* sets it for a screenshot, and reading the type is both more correct than
|
|
72
|
+
* guessing from a name and the reason this file names no image format at all.
|
|
73
|
+
*/
|
|
74
|
+
export declare function collectShots(attachments: readonly ImageAttachment[]): CollectShotsResult;
|
|
75
|
+
/** One image, or one comparison's images, as the report renders them together. */
|
|
76
|
+
export interface ShotGroup {
|
|
77
|
+
base: string;
|
|
78
|
+
/** True when Playwright attached more than one role under this base name. */
|
|
79
|
+
comparison: boolean;
|
|
80
|
+
shots: Shot[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Group a test's shots into what the report shows as a single unit.
|
|
84
|
+
*
|
|
85
|
+
* A `toHaveScreenshot()` failure attaches three or four images that only mean
|
|
86
|
+
* anything together, so they are grouped by their shared base name and ordered
|
|
87
|
+
* expected → previous → actual → diff. Everything else is its own group, in the
|
|
88
|
+
* order it was attached, which is the order the test took them in — the closest
|
|
89
|
+
* thing the report has to a walkthrough of the flow.
|
|
90
|
+
*/
|
|
91
|
+
export declare function groupShots(shots: readonly Shot[]): ShotGroup[];
|
|
92
|
+
/**
|
|
93
|
+
* Drop shots whose file is no longer where the attachment said it was.
|
|
94
|
+
*
|
|
95
|
+
* Two ordinary situations produce them, and both would otherwise fill a
|
|
96
|
+
* QA-facing page with broken image icons — which is the fastest way to make
|
|
97
|
+
* someone stop trusting a report. A merged run group can hold parts captured on
|
|
98
|
+
* other machines, whose paths mean nothing here. And a suite that cleans its
|
|
99
|
+
* output directory between the run and the reading leaves the paths dangling.
|
|
100
|
+
*
|
|
101
|
+
* Missing shots are COUNTED for the caller to warn about, never quietly
|
|
102
|
+
* dropped: "the report shows nine of eleven captures" is a fact the reader needs
|
|
103
|
+
* in order to know they are not looking at everything.
|
|
104
|
+
*/
|
|
105
|
+
export declare function reachableShots(shots: readonly Shot[]): {
|
|
106
|
+
shots: Shot[];
|
|
107
|
+
missing: number;
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=shots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shots.d.ts","sourceRoot":"","sources":["../src/shots.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5E,MAAM,WAAW,IAAI;IACnB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;CACd;AAcD;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAkBD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,GAAG,kBAAkB,CAuBxF;AAED,kFAAkF;AAClF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,EAAE,CAiB9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAUzF"}
|
package/dist/shots.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
/** Fixed so a comparison always reads left-to-right in the same order. */
|
|
3
|
+
const ROLE_ORDER = {
|
|
4
|
+
expected: 0,
|
|
5
|
+
previous: 1,
|
|
6
|
+
actual: 2,
|
|
7
|
+
diff: 3,
|
|
8
|
+
shot: 4,
|
|
9
|
+
};
|
|
10
|
+
/** The suffixes Playwright's snapshot matchers append, longest first. */
|
|
11
|
+
const ROLE_SUFFIXES = ['expected', 'previous', 'actual', 'diff'];
|
|
12
|
+
/** Extension stripped without ever naming an image format. */
|
|
13
|
+
function withoutExtension(name) {
|
|
14
|
+
return name.replace(/\.[A-Za-z0-9]{1,5}$/, '');
|
|
15
|
+
}
|
|
16
|
+
function classify(name) {
|
|
17
|
+
const stem = withoutExtension(name);
|
|
18
|
+
for (const role of ROLE_SUFFIXES) {
|
|
19
|
+
const suffix = `-${role}`;
|
|
20
|
+
if (stem.endsWith(suffix))
|
|
21
|
+
return { base: stem.slice(0, -suffix.length), role };
|
|
22
|
+
}
|
|
23
|
+
return { base: stem, role: 'shot' };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pull the image attachments out of one test result.
|
|
27
|
+
*
|
|
28
|
+
* Two cases are deliberately not equivalent. An attachment with a `path` is a
|
|
29
|
+
* file on disk we can link to. An attachment with only a `body` is an image
|
|
30
|
+
* Playwright is holding in memory — `testInfo.attach(name, { body })` never
|
|
31
|
+
* writes it — and putting it in the report would mean writing the bytes
|
|
32
|
+
* ourselves, which is the one thing this module exists not to do. Those are
|
|
33
|
+
* COUNTED and reported as a warning, because an image the reader cannot see
|
|
34
|
+
* must not silently look like an image that was never taken.
|
|
35
|
+
*
|
|
36
|
+
* Selection is by `contentType` rather than by file extension. Playwright always
|
|
37
|
+
* sets it for a screenshot, and reading the type is both more correct than
|
|
38
|
+
* guessing from a name and the reason this file names no image format at all.
|
|
39
|
+
*/
|
|
40
|
+
export function collectShots(attachments) {
|
|
41
|
+
const shots = [];
|
|
42
|
+
let inMemory = 0;
|
|
43
|
+
for (const attachment of attachments) {
|
|
44
|
+
if (!attachment.contentType?.startsWith('image/'))
|
|
45
|
+
continue;
|
|
46
|
+
if (!attachment.path) {
|
|
47
|
+
if (attachment.body)
|
|
48
|
+
inMemory += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const { base, role } = classify(attachment.name);
|
|
52
|
+
shots.push({
|
|
53
|
+
name: attachment.name,
|
|
54
|
+
base,
|
|
55
|
+
role,
|
|
56
|
+
contentType: attachment.contentType,
|
|
57
|
+
path: attachment.path,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return { shots, inMemory };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Group a test's shots into what the report shows as a single unit.
|
|
64
|
+
*
|
|
65
|
+
* A `toHaveScreenshot()` failure attaches three or four images that only mean
|
|
66
|
+
* anything together, so they are grouped by their shared base name and ordered
|
|
67
|
+
* expected → previous → actual → diff. Everything else is its own group, in the
|
|
68
|
+
* order it was attached, which is the order the test took them in — the closest
|
|
69
|
+
* thing the report has to a walkthrough of the flow.
|
|
70
|
+
*/
|
|
71
|
+
export function groupShots(shots) {
|
|
72
|
+
const groups = new Map();
|
|
73
|
+
for (const shot of shots) {
|
|
74
|
+
const existing = groups.get(shot.base);
|
|
75
|
+
if (existing)
|
|
76
|
+
existing.push(shot);
|
|
77
|
+
else
|
|
78
|
+
groups.set(shot.base, [shot]);
|
|
79
|
+
}
|
|
80
|
+
return [...groups].map(([base, members]) => {
|
|
81
|
+
const roles = new Set(members.map((shot) => shot.role));
|
|
82
|
+
return {
|
|
83
|
+
base,
|
|
84
|
+
comparison: roles.size > 1 || !roles.has('shot'),
|
|
85
|
+
shots: [...members].sort((a, b) => ROLE_ORDER[a.role] - ROLE_ORDER[b.role]),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Drop shots whose file is no longer where the attachment said it was.
|
|
91
|
+
*
|
|
92
|
+
* Two ordinary situations produce them, and both would otherwise fill a
|
|
93
|
+
* QA-facing page with broken image icons — which is the fastest way to make
|
|
94
|
+
* someone stop trusting a report. A merged run group can hold parts captured on
|
|
95
|
+
* other machines, whose paths mean nothing here. And a suite that cleans its
|
|
96
|
+
* output directory between the run and the reading leaves the paths dangling.
|
|
97
|
+
*
|
|
98
|
+
* Missing shots are COUNTED for the caller to warn about, never quietly
|
|
99
|
+
* dropped: "the report shows nine of eleven captures" is a fact the reader needs
|
|
100
|
+
* in order to know they are not looking at everything.
|
|
101
|
+
*/
|
|
102
|
+
export function reachableShots(shots) {
|
|
103
|
+
const reachable = [];
|
|
104
|
+
let missing = 0;
|
|
105
|
+
for (const shot of shots) {
|
|
106
|
+
if (existsSync(shot.path))
|
|
107
|
+
reachable.push(shot);
|
|
108
|
+
else
|
|
109
|
+
missing += 1;
|
|
110
|
+
}
|
|
111
|
+
return { shots: reachable, missing };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=shots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shots.js","sourceRoot":"","sources":["../src/shots.ts"],"names":[],"mappings":"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"}
|
package/dist/style.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface Style {
|
|
2
|
+
bold(value: string): string;
|
|
3
|
+
dim(value: string): string;
|
|
4
|
+
/** For the one or two figures a line exists to deliver. */
|
|
5
|
+
figure(value: string): string;
|
|
6
|
+
heading(value: string): string;
|
|
7
|
+
/** The wordmark, and nothing else. */
|
|
8
|
+
accent(value: string): string;
|
|
9
|
+
on: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function createStyle(env?: NodeJS.ProcessEnv, isTTY?: boolean): Style;
|
|
12
|
+
/**
|
|
13
|
+
* A number in a fixed right-aligned gutter, with its label after it.
|
|
14
|
+
*
|
|
15
|
+
* This is the whole layout idea. Putting every figure in the same column, ahead
|
|
16
|
+
* of its prose, lets a reader scan one narrow strip down the left and take in the
|
|
17
|
+
* shape of the report without reading a word of it. It degrades perfectly to
|
|
18
|
+
* plain text, greps cleanly, and survives being pasted into a narrow Slack window
|
|
19
|
+
* — which a bordered table does not, and which is the main thing that happens to
|
|
20
|
+
* this output.
|
|
21
|
+
*/
|
|
22
|
+
export declare function figureLine(style: Style, value: string | number, label: string, gutter?: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Soft-wrap a long value under the gutter, on word boundaries.
|
|
25
|
+
*
|
|
26
|
+
* Wrapping rather than truncating, for the reason the run summary learned the
|
|
27
|
+
* hard way: a truncated line cuts off the useful half. A repository with five
|
|
28
|
+
* Playwright configs has a config list longer than any terminal, and the fifth
|
|
29
|
+
* name matters as much as the first.
|
|
30
|
+
*/
|
|
31
|
+
export declare function continuationLines(style: Style, value: string, width?: number, gutter?: number): string[];
|
|
32
|
+
/** A section heading. Capitals, so it still reads as a heading with colour stripped. */
|
|
33
|
+
export declare function heading(style: Style, text: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* A heading followed by figure rows and their notes.
|
|
36
|
+
*
|
|
37
|
+
* The one repeated shape in the run summary, extracted because P1b adds five more
|
|
38
|
+
* sections of exactly this form and five hand-rolled copies would drift in
|
|
39
|
+
* spacing. Fifteen lines rather than a section framework: the existing sections
|
|
40
|
+
* keep their bespoke layouts, and nothing here retrofits them.
|
|
41
|
+
*/
|
|
42
|
+
export declare function figureBlock(style: Style, title: string, rows: readonly {
|
|
43
|
+
value: number | string;
|
|
44
|
+
label: string;
|
|
45
|
+
notes?: string[];
|
|
46
|
+
}[], notes?: readonly string[], width?: number): string[];
|
|
47
|
+
//# sourceMappingURL=style.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../src/style.ts"],"names":[],"mappings":"AAkDA,MAAM,WAAW,KAAK;IACpB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,2DAA2D;IAC3D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,sCAAsC;IACtC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,EAAE,EAAE,OAAO,CAAC;CACb;AAED,wBAAgB,WAAW,CACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,KAAK,GAAE,OAAuC,GAC7C,KAAK,CAkCP;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,SAAI,GACT,MAAM,CASR;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,EAAE,MAAM,SAAI,GAAG,MAAM,EAAE,CAkB/F;AAED,wFAAwF;AACxF,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS;IAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,EAC5E,KAAK,GAAE,SAAS,MAAM,EAAO,EAC7B,KAAK,SAAK,GACT,MAAM,EAAE,CAcV"}
|
package/dist/style.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* DUPLICATED FROM `packages/review-cli/src/style.ts`, deliberately, and the reason is
|
|
3
|
+
* structural rather than laziness.
|
|
4
|
+
*
|
|
5
|
+
* Decision 9 (2026-08-17) makes the CLI unable to load into a customer's test
|
|
6
|
+
* process — that is the whole point of the two-package split, and it is enforced
|
|
7
|
+
* by there being no import path rather than by a convention anybody has to
|
|
8
|
+
* remember. The SDK does declare `@setsquare/review-cli` as a dependency, but only so
|
|
9
|
+
* one install links the `setsquare` binary; importing CLI code from the reporter would
|
|
10
|
+
* pull the audit, its parser and its adapters into the import graph of every test
|
|
11
|
+
* run, which is exactly what the split exists to prevent.
|
|
12
|
+
*
|
|
13
|
+
* So sixty zero-dependency lines are copied instead. The alternative — a third
|
|
14
|
+
* published package for a string-wrapping helper — costs more to sign, publish
|
|
15
|
+
* and review each release than this costs to keep in step, and the two are held
|
|
16
|
+
* in step by the assertion that matters being made in both suites: strip the
|
|
17
|
+
* escapes and the output is unchanged.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The small amount of terminal styling this product allows itself.
|
|
21
|
+
*
|
|
22
|
+
* The governing rule predates this file and does not bend: **colour is
|
|
23
|
+
* decoration, never information.** Every line must carry its full meaning in
|
|
24
|
+
* plain text, because we sell accessibility and because CI logs, pipes and pasted
|
|
25
|
+
* output all lose colour. Nothing here may be the only thing telling a reader
|
|
26
|
+
* that something failed.
|
|
27
|
+
*
|
|
28
|
+
* What styling IS for is hierarchy — making the number a person came for land
|
|
29
|
+
* before the sentence explaining it. That is a real problem rather than a vanity
|
|
30
|
+
* one: a well-structured report where every line carries equal weight reads as a
|
|
31
|
+
* wall, and the reader ends up doing the tool's job of working out what matters.
|
|
32
|
+
*
|
|
33
|
+
* Detection follows the informal standard rather than our preference: an explicit
|
|
34
|
+
* NO_COLOR of any value wins over everything, then FORCE_COLOR, then whether we
|
|
35
|
+
* are actually talking to a terminal. CI is treated as no-colour deliberately —
|
|
36
|
+
* build logs are read as text far more often than they are watched live.
|
|
37
|
+
*
|
|
38
|
+
* Escapes are written as escape SEQUENCES rather than literal control bytes. A
|
|
39
|
+
* literal escape makes the file binary to grep and awkward to diff, and a styling
|
|
40
|
+
* module is precisely the file nobody should have to fight to read.
|
|
41
|
+
*/
|
|
42
|
+
function enabled(env, isTTY) {
|
|
43
|
+
if (env['NO_COLOR'] !== undefined)
|
|
44
|
+
return false;
|
|
45
|
+
if (env['FORCE_COLOR'] !== undefined)
|
|
46
|
+
return env['FORCE_COLOR'] !== '0';
|
|
47
|
+
if (env['CI'] !== undefined)
|
|
48
|
+
return false;
|
|
49
|
+
return isTTY;
|
|
50
|
+
}
|
|
51
|
+
export function createStyle(env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
|
|
52
|
+
const on = enabled(env, isTTY);
|
|
53
|
+
const wrap = (code) => (value) => on ? `\u001B[${code}m${value}\u001B[0m` : value;
|
|
54
|
+
return {
|
|
55
|
+
on,
|
|
56
|
+
bold: wrap('1'),
|
|
57
|
+
dim: wrap('2'),
|
|
58
|
+
/*
|
|
59
|
+
* Figures stay BOLD, deliberately, while headings get hue.
|
|
60
|
+
*
|
|
61
|
+
* The numbers are the content — they must read as figures on a monochrome
|
|
62
|
+
* terminal, in a pipe, and to anyone whose colour perception differs from
|
|
63
|
+
* ours, so they lean on weight, which survives all three. Hue is spent on
|
|
64
|
+
* structure instead: headings and the wordmark, where losing it costs a
|
|
65
|
+
* reader nothing but noticing it helps them find their place.
|
|
66
|
+
*/
|
|
67
|
+
figure: wrap('1'),
|
|
68
|
+
/*
|
|
69
|
+
* The basic 16-colour codes rather than 256-colour or truecolour, and that is
|
|
70
|
+
* an accessibility decision rather than a compatibility one.
|
|
71
|
+
*
|
|
72
|
+
* Codes 30-37 are remapped by the terminal to the user's OWN theme, so cyan on
|
|
73
|
+
* a light Solarized profile and cyan on a dark one are both legible because
|
|
74
|
+
* the person chose them. A hard-coded #7c3aed looks right in our terminal and
|
|
75
|
+
* can be genuinely unreadable in someone else's — and picking an unreadable
|
|
76
|
+
* colour would be a poor advertisement for an accessibility company.
|
|
77
|
+
*/
|
|
78
|
+
heading: wrap('1;36'),
|
|
79
|
+
// Bold folded in, so callers never nest two wrappers and emit a double reset.
|
|
80
|
+
accent: wrap('1;35'),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A number in a fixed right-aligned gutter, with its label after it.
|
|
85
|
+
*
|
|
86
|
+
* This is the whole layout idea. Putting every figure in the same column, ahead
|
|
87
|
+
* of its prose, lets a reader scan one narrow strip down the left and take in the
|
|
88
|
+
* shape of the report without reading a word of it. It degrades perfectly to
|
|
89
|
+
* plain text, greps cleanly, and survives being pasted into a narrow Slack window
|
|
90
|
+
* — which a bordered table does not, and which is the main thing that happens to
|
|
91
|
+
* this output.
|
|
92
|
+
*/
|
|
93
|
+
export function figureLine(style, value, label, gutter = 7) {
|
|
94
|
+
const shown = typeof value === 'number' ? value.toLocaleString('en-GB') : value;
|
|
95
|
+
// An empty gutter is a continuation line, not a figure. Styling it emits escape
|
|
96
|
+
// codes around nothing, which is invisible on screen and litters any log that
|
|
97
|
+
// keeps them.
|
|
98
|
+
if (shown === '')
|
|
99
|
+
return ` ${' '.repeat(gutter)} ${label}`;
|
|
100
|
+
return ` ${style.figure(String(shown).padStart(gutter))} ${label}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Soft-wrap a long value under the gutter, on word boundaries.
|
|
104
|
+
*
|
|
105
|
+
* Wrapping rather than truncating, for the reason the run summary learned the
|
|
106
|
+
* hard way: a truncated line cuts off the useful half. A repository with five
|
|
107
|
+
* Playwright configs has a config list longer than any terminal, and the fifth
|
|
108
|
+
* name matters as much as the first.
|
|
109
|
+
*/
|
|
110
|
+
export function continuationLines(style, value, width = 80, gutter = 7) {
|
|
111
|
+
const indent = ' '.repeat(gutter + 4);
|
|
112
|
+
const room = Math.max(24, width - indent.length);
|
|
113
|
+
const lines = [];
|
|
114
|
+
let current = '';
|
|
115
|
+
for (const word of value.split(' ')) {
|
|
116
|
+
if (current !== '' && `${current} ${word}`.length > room) {
|
|
117
|
+
lines.push(`${indent}${style.dim(current)}`);
|
|
118
|
+
current = word;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
current = current === '' ? word : `${current} ${word}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (current !== '')
|
|
125
|
+
lines.push(`${indent}${style.dim(current)}`);
|
|
126
|
+
return lines;
|
|
127
|
+
}
|
|
128
|
+
/** A section heading. Capitals, so it still reads as a heading with colour stripped. */
|
|
129
|
+
export function heading(style, text) {
|
|
130
|
+
return ` ${style.heading(text.toUpperCase())}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A heading followed by figure rows and their notes.
|
|
134
|
+
*
|
|
135
|
+
* The one repeated shape in the run summary, extracted because P1b adds five more
|
|
136
|
+
* sections of exactly this form and five hand-rolled copies would drift in
|
|
137
|
+
* spacing. Fifteen lines rather than a section framework: the existing sections
|
|
138
|
+
* keep their bespoke layouts, and nothing here retrofits them.
|
|
139
|
+
*/
|
|
140
|
+
export function figureBlock(style, title, rows, notes = [], width = 80) {
|
|
141
|
+
if (rows.length === 0 && notes.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
const lines = [heading(style, title)];
|
|
144
|
+
for (const row of rows) {
|
|
145
|
+
lines.push(figureLine(style, row.value, row.label));
|
|
146
|
+
for (const note of row.notes ?? [])
|
|
147
|
+
lines.push(...continuationLines(style, note, width));
|
|
148
|
+
}
|
|
149
|
+
for (const note of notes)
|
|
150
|
+
lines.push(...continuationLines(style, note, width));
|
|
151
|
+
lines.push('');
|
|
152
|
+
return lines;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.js","sourceRoot":"","sources":["../src/style.ts"],"names":[],"mappings":"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"}
|
package/dist/suite.d.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reductions over Playwright's own `TestResult`, which the reporter used to drop.
|
|
3
|
+
*
|
|
4
|
+
* `onTestEnd` read six fields and discarded the rest. What was on the object:
|
|
5
|
+
* `steps` (a tree, with timings), `errors`, `workerIndex`, `annotations`. From
|
|
6
|
+
* that, with no new instrumentation, come three things nothing else in the run
|
|
7
|
+
* can say:
|
|
8
|
+
*
|
|
9
|
+
* - where the suite's wall clock actually goes, down to the step
|
|
10
|
+
* - which worker held the run open, which is the commonest cheap CI win and is
|
|
11
|
+
* invisible in Playwright's own HTML report
|
|
12
|
+
* - why a test failed, so `run.json` can be triaged by whoever it is handed to
|
|
13
|
+
* rather than only by whoever watched it happen
|
|
14
|
+
*
|
|
15
|
+
* Kept out of `reporter.ts` because these are pure functions over plain data and
|
|
16
|
+
* deserve to be tested as such. The reporter's job is to call them.
|
|
17
|
+
*
|
|
18
|
+
* NOT here, and never: `result.stdout`. It is arbitrary customer application and
|
|
19
|
+
* test log output — the highest-PII surface on the object — and no cap makes it
|
|
20
|
+
* safe to carry. A grep assertion in `observe.test.ts` keeps it that way.
|
|
21
|
+
*/
|
|
22
|
+
/** The subset of Playwright's `TestStep` this module needs. Structural, so a literal works in tests. */
|
|
23
|
+
export interface StepLike {
|
|
24
|
+
title: string;
|
|
25
|
+
category: string;
|
|
26
|
+
duration: number;
|
|
27
|
+
error?: unknown;
|
|
28
|
+
steps?: StepLike[];
|
|
29
|
+
}
|
|
30
|
+
export interface SlowStep {
|
|
31
|
+
title: string;
|
|
32
|
+
category: string;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
}
|
|
35
|
+
export interface TestFailureFacts {
|
|
36
|
+
errorClass?: string;
|
|
37
|
+
message?: string;
|
|
38
|
+
step?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function errorClassOf(message: string): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Collapse a message to one line and cap it.
|
|
43
|
+
*
|
|
44
|
+
* A Playwright error message is natively multi-line — assertion diff, call log,
|
|
45
|
+
* code frame — and a newline is a C0 control, which the contract's character
|
|
46
|
+
* constraint bans. Collapsing rather than permitting newlines is a deliberate
|
|
47
|
+
* contract decision recorded in `ingest.ts`: one sanitisation rule, not two that
|
|
48
|
+
* can drift. The repro command is how someone gets the readable original.
|
|
49
|
+
*/
|
|
50
|
+
export declare function collapseMessage(value: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* The deepest failing step's title.
|
|
53
|
+
*
|
|
54
|
+
* Deepest, not first: Playwright marks every ancestor of a failure as failed too,
|
|
55
|
+
* so the outermost failing step is usually `Before Hooks` or the test body — true,
|
|
56
|
+
* and useless. The innermost one is the actual operation that broke.
|
|
57
|
+
*/
|
|
58
|
+
export declare function deepestFailingStep(steps: readonly StepLike[] | undefined): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Why this test failed, from the error list and the step tree.
|
|
61
|
+
*
|
|
62
|
+
* Returns `undefined` for a test that did not fail, and for a failed test whose
|
|
63
|
+
* result carried neither an error nor an identifiable failing step — because
|
|
64
|
+
* `{}` would imply we looked and found a failure with no properties, which is not
|
|
65
|
+
* what happened.
|
|
66
|
+
*/
|
|
67
|
+
export declare function failureFacts(errors: readonly {
|
|
68
|
+
message?: string;
|
|
69
|
+
}[] | undefined, steps: readonly StepLike[] | undefined): TestFailureFacts | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* The slowest LEAF steps.
|
|
72
|
+
*
|
|
73
|
+
* Leaves only, and that is the load-bearing decision. Playwright's step tree
|
|
74
|
+
* nests, and a parent's duration CONTAINS its children's — so a list mixing the
|
|
75
|
+
* two double-counts, and "the slowest step took 8s" would be answered by a
|
|
76
|
+
* wrapper whose children did all the work. Leaves are the actual operations.
|
|
77
|
+
*
|
|
78
|
+
* The cost of that choice, stated: a slow custom step that wraps many fast ones
|
|
79
|
+
* will not appear, even though it is arguably the interesting row. Reporting both
|
|
80
|
+
* would need a tree in the output, and the useful artefact here is a short list.
|
|
81
|
+
*/
|
|
82
|
+
export declare function slowestSteps(steps: readonly StepLike[] | undefined, limit?: number): SlowStep[];
|
|
83
|
+
export interface WorkerLoad {
|
|
84
|
+
/**
|
|
85
|
+
* Distinct worker PROCESSES that ran at least one test.
|
|
86
|
+
*
|
|
87
|
+
* **Not the parallelism.** Playwright recycles workers — a new process per
|
|
88
|
+
* project, and another after a worker dies — so a strictly serial suite still
|
|
89
|
+
* reports several. Measured on the SDK's own fixture suite: `workers: 1` in the
|
|
90
|
+
* config, five worker indices across five projects.
|
|
91
|
+
*
|
|
92
|
+
* `configured` below is the parallelism, and it is what decides whether any of
|
|
93
|
+
* this is worth showing.
|
|
94
|
+
*/
|
|
95
|
+
workers: number;
|
|
96
|
+
/**
|
|
97
|
+
* `workers` from the Playwright config, when the caller knows it.
|
|
98
|
+
*
|
|
99
|
+
* Load below is only an IMBALANCE if more than one worker could run at once. On
|
|
100
|
+
* a serial suite the spread between processes is just the shape of the projects,
|
|
101
|
+
* and presenting it as imbalance would be a confident wrong answer — which is
|
|
102
|
+
* how this field came to exist: the first version reported `tailMs: 6350` on a
|
|
103
|
+
* one-worker run and read as a serious finding.
|
|
104
|
+
*
|
|
105
|
+
* Absent when the config could not be read; renderers must then withhold the
|
|
106
|
+
* imbalance framing rather than assume parallelism.
|
|
107
|
+
*/
|
|
108
|
+
configured?: number;
|
|
109
|
+
/** Busy milliseconds on the busiest worker process. */
|
|
110
|
+
busiestMs: number;
|
|
111
|
+
/** Busy milliseconds on the least busy worker process. */
|
|
112
|
+
idlestMs: number;
|
|
113
|
+
/**
|
|
114
|
+
* How far the busiest worker sits above the median.
|
|
115
|
+
*
|
|
116
|
+
* Meaningful only when `configured` is greater than 1, where a large value means
|
|
117
|
+
* one worker is holding the run open while the others are idle — the commonest
|
|
118
|
+
* cheap CI win. On a serial run it is an artefact and must not be shown.
|
|
119
|
+
*/
|
|
120
|
+
tailMs: number;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Per-worker busy time.
|
|
124
|
+
*
|
|
125
|
+
* **Busy time, never wall clock.** These are summed test durations, so the total
|
|
126
|
+
* across workers exceeds the run's elapsed time by roughly the parallelism factor.
|
|
127
|
+
* Nothing may present these as "the run took X" or subtract them from anything —
|
|
128
|
+
* the same discipline scan overhead follows, and for the same reason: we can
|
|
129
|
+
* measure what we spent, not what the run would have cost without it.
|
|
130
|
+
*
|
|
131
|
+
* `undefined` when no outcome carried a worker index, which is the honest answer
|
|
132
|
+
* for a run where Playwright never told us (and for envelopes written before the
|
|
133
|
+
* field existed). Renderers must then say nothing rather than reporting one
|
|
134
|
+
* worker.
|
|
135
|
+
*/
|
|
136
|
+
export declare function workerLoad(outcomes: readonly {
|
|
137
|
+
workerIndex?: number;
|
|
138
|
+
durationMs: number;
|
|
139
|
+
}[], configured?: number): WorkerLoad | undefined;
|
|
140
|
+
export interface FlakyTest {
|
|
141
|
+
testId: string;
|
|
142
|
+
/** Which attempt finally passed. */
|
|
143
|
+
passedOnRetry: number;
|
|
144
|
+
/** How the earliest failing attempt failed, when the result said. */
|
|
145
|
+
failure?: TestFailureFacts;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Tests that failed and then passed, itemised.
|
|
149
|
+
*
|
|
150
|
+
* The summary already says "2 flaky". It does not say WHICH, on which attempt, or
|
|
151
|
+
* on which step — and all three are already in the data, which is the whole
|
|
152
|
+
* complaint. No schema change was needed for this; it is a projection over
|
|
153
|
+
* `(testId, retry, status)` plus the failure facts captured alongside.
|
|
154
|
+
*/
|
|
155
|
+
interface Attempt {
|
|
156
|
+
testId: string;
|
|
157
|
+
retry: number;
|
|
158
|
+
status: string;
|
|
159
|
+
failure?: TestFailureFacts;
|
|
160
|
+
}
|
|
161
|
+
export declare function flakyTests(outcomes: readonly Attempt[]): FlakyTest[];
|
|
162
|
+
export interface OverheadFacts {
|
|
163
|
+
scans: number;
|
|
164
|
+
scanMs: number;
|
|
165
|
+
axeMs: number;
|
|
166
|
+
runDurationMs: number;
|
|
167
|
+
}
|
|
168
|
+
export interface SuiteFacts {
|
|
169
|
+
slowestTests: {
|
|
170
|
+
testId: string;
|
|
171
|
+
durationMs: number;
|
|
172
|
+
}[];
|
|
173
|
+
slowestSteps: {
|
|
174
|
+
testId: string;
|
|
175
|
+
title: string;
|
|
176
|
+
category: string;
|
|
177
|
+
durationMs: number;
|
|
178
|
+
}[];
|
|
179
|
+
workers?: WorkerLoad;
|
|
180
|
+
flaky: FlakyTest[];
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The shape `localFacts` needs, described structurally rather than imported.
|
|
184
|
+
*
|
|
185
|
+
* `TestOutcome` lives in `envelope.ts` and `RunMeta` in `contract.ts`, and both
|
|
186
|
+
* already depend on this module for `FlakyTest`/`WorkerLoad`. Naming the input
|
|
187
|
+
* structurally keeps the dependency pointing one way — and it is what lets both
|
|
188
|
+
* the solo reporter and the run-group merge call this with their own arrays.
|
|
189
|
+
*/
|
|
190
|
+
export interface OutcomeLike {
|
|
191
|
+
testId: string;
|
|
192
|
+
durationMs: number;
|
|
193
|
+
retry: number;
|
|
194
|
+
status: string;
|
|
195
|
+
workerIndex?: number;
|
|
196
|
+
failure?: TestFailureFacts;
|
|
197
|
+
slowestSteps?: SlowStep[];
|
|
198
|
+
records: readonly {
|
|
199
|
+
kind: string;
|
|
200
|
+
cost?: {
|
|
201
|
+
totalMs: number;
|
|
202
|
+
axeMs: number;
|
|
203
|
+
};
|
|
204
|
+
}[];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The local-only suite/overhead/coverage blocks for `meta.json`.
|
|
208
|
+
*
|
|
209
|
+
* Assembled in one place because all three are reductions over the same
|
|
210
|
+
* `outcomes` array, and because a renderer recomputing any of them is how the
|
|
211
|
+
* terminal, the HTML report and the markdown output come to disagree.
|
|
212
|
+
*/
|
|
213
|
+
export declare function localFacts(outcomes: readonly OutcomeLike[], runDurationMs: number, configuredWorkers?: number): {
|
|
214
|
+
overhead?: OverheadFacts;
|
|
215
|
+
suite: SuiteFacts;
|
|
216
|
+
};
|
|
217
|
+
export {};
|
|
218
|
+
//# sourceMappingURL=suite.d.ts.map
|