tuffgal 0.1.0-alpha.9 → 0.2.0-alpha.2
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/CHANGELOG.md +149 -2
- package/README.md +78 -3
- package/dist/cli.d.ts +50 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +204 -18
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/config.d.ts +38 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -1
- package/dist/config.js.map +1 -1
- package/dist/coverage/screens.d.ts +11 -6
- package/dist/coverage/screens.d.ts.map +1 -1
- package/dist/coverage/screens.js +12 -7
- package/dist/coverage/screens.js.map +1 -1
- package/dist/reporter/assets/report.css +415 -51
- package/dist/reporter/assets/report.js +502 -68
- package/dist/reporter/template.d.ts +8 -1
- package/dist/reporter/template.d.ts.map +1 -1
- package/dist/reporter/template.js +517 -85
- package/dist/reporter/template.js.map +1 -1
- package/dist/reporter/writeReport.d.ts +2 -2
- package/dist/reporter/writeReport.d.ts.map +1 -1
- package/dist/reporter/writeReport.js +3 -3
- package/dist/reporter/writeReport.js.map +1 -1
- package/dist/runner/approve.d.ts +34 -9
- package/dist/runner/approve.d.ts.map +1 -1
- package/dist/runner/approve.js +54 -10
- package/dist/runner/approve.js.map +1 -1
- package/dist/runner/approveFrom.d.ts +58 -0
- package/dist/runner/approveFrom.d.ts.map +1 -0
- package/dist/runner/approveFrom.js +384 -0
- package/dist/runner/approveFrom.js.map +1 -0
- package/dist/runner/breakpointPasses.d.ts +39 -0
- package/dist/runner/breakpointPasses.d.ts.map +1 -0
- package/dist/runner/breakpointPasses.js +107 -0
- package/dist/runner/breakpointPasses.js.map +1 -0
- package/dist/runner/bridges/database.d.ts +4 -3
- package/dist/runner/bridges/database.d.ts.map +1 -1
- package/dist/runner/bridges/database.js +4 -3
- package/dist/runner/bridges/database.js.map +1 -1
- package/dist/runner/exitCode.d.ts +34 -0
- package/dist/runner/exitCode.d.ts.map +1 -0
- package/dist/runner/exitCode.js +41 -0
- package/dist/runner/exitCode.js.map +1 -0
- package/dist/runner/manifest.d.ts +149 -0
- package/dist/runner/manifest.d.ts.map +1 -0
- package/dist/runner/manifest.js +229 -0
- package/dist/runner/manifest.js.map +1 -0
- package/dist/runner/mode.d.ts +41 -0
- package/dist/runner/mode.d.ts.map +1 -0
- package/dist/runner/mode.js +43 -0
- package/dist/runner/mode.js.map +1 -0
- package/dist/runner/orphanScan.d.ts +68 -0
- package/dist/runner/orphanScan.d.ts.map +1 -0
- package/dist/runner/orphanScan.js +150 -0
- package/dist/runner/orphanScan.js.map +1 -0
- package/dist/runner/run.d.ts +85 -1
- package/dist/runner/run.d.ts.map +1 -1
- package/dist/runner/run.js +267 -41
- package/dist/runner/run.js.map +1 -1
- package/dist/runner/runAction.d.ts +16 -0
- package/dist/runner/runAction.d.ts.map +1 -1
- package/dist/runner/runAction.js +112 -13
- package/dist/runner/runAction.js.map +1 -1
- package/dist/runner/runStory.d.ts +15 -4
- package/dist/runner/runStory.d.ts.map +1 -1
- package/dist/runner/runStory.js +41 -19
- package/dist/runner/runStory.js.map +1 -1
- package/dist/runner/storyFilter.d.ts +8 -0
- package/dist/runner/storyFilter.d.ts.map +1 -1
- package/dist/runner/storyFilter.js +11 -0
- package/dist/runner/storyFilter.js.map +1 -1
- package/dist/schema/action.d.ts +11 -11
- package/dist/schema/result.d.ts +125 -6
- package/dist/schema/result.d.ts.map +1 -1
- package/dist/schema/result.js.map +1 -1
- package/dist/screenshots/baselineStore.d.ts +92 -10
- package/dist/screenshots/baselineStore.d.ts.map +1 -1
- package/dist/screenshots/baselineStore.js +102 -18
- package/dist/screenshots/baselineStore.js.map +1 -1
- package/dist/screenshots/capture.d.ts +10 -5
- package/dist/screenshots/capture.d.ts.map +1 -1
- package/dist/screenshots/capture.js +31 -18
- package/dist/screenshots/capture.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, copyFile, mkdir, readFile, rm, writeFile, } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join, relative } from 'node:path';
|
|
3
|
+
import { PNG } from 'pngjs';
|
|
3
4
|
/**
|
|
4
5
|
* Reduces an arbitrary breakpoint name to a filesystem-safe path segment.
|
|
5
6
|
* Breakpoint names are simple lowercase identifiers today (`mobile`, `tablet`,
|
|
@@ -12,33 +13,69 @@ function breakpointSegment(breakpoint) {
|
|
|
12
13
|
return breakpoint.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* The single owner of the `<report>/candidates/` path. The runner writes the
|
|
17
|
+
* candidate tree + a `results.json` copy here, and `approve --from` reads that
|
|
18
|
+
* same tree back. Centralised so the literal `'candidates'` segment has one
|
|
19
|
+
* definition instead of drifting across the runner, the store, and approve.
|
|
20
|
+
*/
|
|
21
|
+
export function candidatesDir(reportDir) {
|
|
22
|
+
return join(reportDir, 'candidates');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The action-name shape the action schema enforces (`/^[a-z0-9-]+$/`,
|
|
26
|
+
* lowercase-kebab). Re-exported here so the trust boundary in `approve --from`
|
|
27
|
+
* can reject a directory name from an untrusted candidate artifact against the
|
|
28
|
+
* exact same invariant the runner produced its layout under, without importing
|
|
29
|
+
* the zod schema. Kept in lock-step with `schema/action.ts` by intent.
|
|
30
|
+
*/
|
|
31
|
+
export const ACTION_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
32
|
+
/**
|
|
33
|
+
* The filesystem-safe breakpoint segment shape (`/^[a-z0-9_-]+$/`) that {@link
|
|
34
|
+
* breakpointSegment} emits. `approve --from` validates a candidate PNG's
|
|
35
|
+
* breakpoint stem against this so a name outside the runner's own output shape
|
|
36
|
+
* (path separators, dots, traversal) can never reach a write.
|
|
37
|
+
*/
|
|
38
|
+
export const BREAKPOINT_SEGMENT_PATTERN = /^[a-z0-9_-]+$/;
|
|
39
|
+
/**
|
|
40
|
+
* Computes deterministic paths for the baseline (the comparison target —
|
|
41
|
+
* committed baselines by default, or the local cache when `comparisonRoot` is
|
|
42
|
+
* supplied), actual (regenerated each run), diff (regenerated when a baseline
|
|
43
|
+
* existed and the diff was non-zero), and candidate (proposed-new-baseline for
|
|
44
|
+
* CI approval) PNGs. Centralised so the runner and the CLI's `approve` command
|
|
45
|
+
* agree on layout.
|
|
19
46
|
*
|
|
20
47
|
* Every path is keyed by both action name and breakpoint so per-breakpoint
|
|
21
|
-
* captures of the same action stay isolated:
|
|
22
|
-
* filename under the action directory
|
|
23
|
-
* report-side artifacts splice
|
|
24
|
-
* (`<action>.<breakpoint>.actual.png`) since they already
|
|
25
|
-
* directory.
|
|
48
|
+
* captures of the same action stay isolated: baseline- and candidate-side paths
|
|
49
|
+
* nest the breakpoint as a filename under the action directory
|
|
50
|
+
* (`<action>/<breakpoint>.png`), while report-side actual/diff artifacts splice
|
|
51
|
+
* it into the filename (`<action>.<breakpoint>.actual.png`) since they already
|
|
52
|
+
* share one per-story directory.
|
|
53
|
+
*
|
|
54
|
+
* The baseline side resolves under `comparisonRoot` (default `baselinesDir`), so
|
|
55
|
+
* omitting the root reproduces the committed-baselines layout unchanged; the
|
|
56
|
+
* candidate and report-side paths are always report-rooted and ignore it.
|
|
26
57
|
*/
|
|
27
58
|
export function pathsFor(options) {
|
|
28
59
|
const storySlug = options.storyFile.replace(/\.json$/i, '');
|
|
29
60
|
const bp = breakpointSegment(options.breakpoint);
|
|
61
|
+
const comparisonRoot = options.comparisonRoot ?? options.baselinesDir;
|
|
30
62
|
return {
|
|
31
|
-
baseline: join(
|
|
63
|
+
baseline: join(comparisonRoot, options.actionName, `${bp}.png`),
|
|
32
64
|
actual: join(options.reportDir, 'screenshots', storySlug, `${options.actionName}.${bp}.actual.png`),
|
|
33
65
|
diff: join(options.reportDir, 'screenshots', storySlug, `${options.actionName}.${bp}.diff.png`),
|
|
34
|
-
a11yBaseline: join(
|
|
66
|
+
a11yBaseline: join(comparisonRoot, options.actionName, `${bp}.a11y.yaml`),
|
|
35
67
|
a11yActual: join(options.reportDir, 'screenshots', storySlug, `${options.actionName}.${bp}.a11y.yaml`),
|
|
68
|
+
// Candidate tree mirrors the baseline layout under the report so a CI
|
|
69
|
+
// approval is a plain copy of `<report>/candidates/` into `paths.baselines`.
|
|
70
|
+
candidate: join(candidatesDir(options.reportDir), options.actionName, `${bp}.png`),
|
|
71
|
+
a11yCandidate: join(candidatesDir(options.reportDir), options.actionName, `${bp}.a11y.yaml`),
|
|
36
72
|
// Pre-breakpoint locations. A project baselined before this feature has
|
|
37
73
|
// its only committed snapshot here; the runner reads `baseline` first and
|
|
38
74
|
// falls back to these when the breakpoint-keyed file is absent, so existing
|
|
39
|
-
// baselines keep matching instead of all reading as `new`.
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
// baselines keep matching instead of all reading as `new`. Rooted at the
|
|
76
|
+
// comparison root like the breakpoint-keyed baseline above.
|
|
77
|
+
legacyBaseline: join(comparisonRoot, options.actionName, '0.png'),
|
|
78
|
+
legacyA11yBaseline: join(comparisonRoot, options.actionName, 'a11y.yaml'),
|
|
42
79
|
};
|
|
43
80
|
}
|
|
44
81
|
/**
|
|
@@ -86,9 +123,47 @@ export async function writeText(path, content) {
|
|
|
86
123
|
await mkdir(dirname(path), { recursive: true });
|
|
87
124
|
await writeFile(path, content, 'utf8');
|
|
88
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Losslessly recompresses a PNG buffer. Playwright emits PNGs tuned for capture
|
|
128
|
+
* speed, not size; re-encoding through pngjs applies its default
|
|
129
|
+
* `deflateLevel: 9` plus adaptive per-scanline filtering (filter type chosen per
|
|
130
|
+
* row from the full `[0..4]` set), which typically shrinks the file with zero
|
|
131
|
+
* pixel change. The decode→re-encode round-trip is lossless: pngjs reads and
|
|
132
|
+
* writes 8-bit RGBA, so the recompressed buffer decodes to a byte-identical
|
|
133
|
+
* pixel array (`recompress.test.ts` asserts this).
|
|
134
|
+
*
|
|
135
|
+
* Two safety guarantees:
|
|
136
|
+
* - Never grows a file. A source that is already densely packed (e.g. a
|
|
137
|
+
* future oxipng-optimised baseline flowing through `approve --from`) can
|
|
138
|
+
* re-encode *larger*; in that case the original buffer is returned unchanged.
|
|
139
|
+
* - Never corrupts. Any decode/encode failure (a non-PNG buffer, a codec edge
|
|
140
|
+
* case) is swallowed and the original buffer is returned, so a recompress
|
|
141
|
+
* miss degrades to "write the bytes we were given" rather than a torn file.
|
|
142
|
+
*
|
|
143
|
+
* This is the single lossless-recompress seam for every PNG the tool writes:
|
|
144
|
+
* `writePng` routes through it, so baseline, candidate, cache, actual, and diff
|
|
145
|
+
* writes all inherit it without per-callsite wiring. Deterministic — pngjs's
|
|
146
|
+
* encoder is a pure function of the pixel data and the fixed options above.
|
|
147
|
+
*
|
|
148
|
+
* Deliberately NOT palette-quantised and never lossy: colour type and bit depth
|
|
149
|
+
* are preserved by the RGBA round-trip.
|
|
150
|
+
*/
|
|
151
|
+
export function recompressPng(png) {
|
|
152
|
+
let recompressed;
|
|
153
|
+
try {
|
|
154
|
+
recompressed = PNG.sync.write(PNG.sync.read(png));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Not a decodable PNG, or an encoder edge case — write what we were given
|
|
158
|
+
// rather than risk emitting a corrupt or truncated file.
|
|
159
|
+
return png;
|
|
160
|
+
}
|
|
161
|
+
// A recompress that grows the file is a loss, not a win — keep the smaller.
|
|
162
|
+
return recompressed.length < png.length ? recompressed : png;
|
|
163
|
+
}
|
|
89
164
|
export async function writePng(path, png) {
|
|
90
165
|
await mkdir(dirname(path), { recursive: true });
|
|
91
|
-
await writeFile(path, png);
|
|
166
|
+
await writeFile(path, recompressPng(png));
|
|
92
167
|
}
|
|
93
168
|
export async function deleteIfExists(path) {
|
|
94
169
|
try {
|
|
@@ -98,9 +173,18 @@ export async function deleteIfExists(path) {
|
|
|
98
173
|
// file was not there; nothing to do
|
|
99
174
|
}
|
|
100
175
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Copies an already-written PNG (the run's `actual`, or a `candidate`) to a new
|
|
178
|
+
* destination verbatim. No recompress here on purpose: the source was produced
|
|
179
|
+
* by `writePng`, which already ran `recompressPng`, so the bytes on disk are the
|
|
180
|
+
* recompressed ones. Copying them forward keeps the destination losslessly
|
|
181
|
+
* recompressed for free. The only non-test caller is `approve` refreshing the
|
|
182
|
+
* per-machine cache; `approve --from` does NOT use this — it reads each source
|
|
183
|
+
* once during validation and writes the retained buffer through {@link writePng}.
|
|
184
|
+
*/
|
|
185
|
+
export async function copyRecompressedPng(actualPath, destinationPath) {
|
|
186
|
+
await mkdir(dirname(destinationPath), { recursive: true });
|
|
187
|
+
await copyFile(actualPath, destinationPath);
|
|
104
188
|
}
|
|
105
189
|
/**
|
|
106
190
|
* Turns an absolute screenshot path into a path that is relative to the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baselineStore.js","sourceRoot":"","sources":["../../src/screenshots/baselineStore.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,EAAE,EACF,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"baselineStore.js","sourceRoot":"","sources":["../../src/screenshots/baselineStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Locator, Page } from 'playwright';
|
|
2
|
+
import type { CaptureMode } from '../config.ts';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Screenshot with animations disabled, caret hidden, and any masks applied so
|
|
5
|
+
* the same UI renders to bit-identical pixels across runs. Masks are Playwright
|
|
6
|
+
* locators whose bounding boxes are blacked out before the image is encoded —
|
|
7
|
+
* use them to neutralise randomised or time-based regions.
|
|
8
|
+
*
|
|
9
|
+
* `mode` controls scope: `viewport` (default) captures only the page's current
|
|
10
|
+
* viewport box — what the user sees above the fold — while `fullPage`
|
|
11
|
+
* composites the entire scrollable document however tall.
|
|
7
12
|
*/
|
|
8
|
-
export declare function capturePage(page: Page, masks?: Locator[]): Promise<Buffer>;
|
|
13
|
+
export declare function capturePage(page: Page, masks?: Locator[], mode?: CaptureMode): Promise<Buffer>;
|
|
9
14
|
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/screenshots/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhD
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/screenshots/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,KAAK,GAAE,OAAO,EAAO,EACrB,IAAI,GAAE,WAAwB,GAC7B,OAAO,CAAC,MAAM,CAAC,CA8BjB"}
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Screenshot with animations disabled, caret hidden, and any masks applied so
|
|
3
|
+
* the same UI renders to bit-identical pixels across runs. Masks are Playwright
|
|
4
|
+
* locators whose bounding boxes are blacked out before the image is encoded —
|
|
5
|
+
* use them to neutralise randomised or time-based regions.
|
|
6
|
+
*
|
|
7
|
+
* `mode` controls scope: `viewport` (default) captures only the page's current
|
|
8
|
+
* viewport box — what the user sees above the fold — while `fullPage`
|
|
9
|
+
* composites the entire scrollable document however tall.
|
|
6
10
|
*/
|
|
7
|
-
export async function capturePage(page, masks = []) {
|
|
8
|
-
//
|
|
9
|
-
// the
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
11
|
+
export async function capturePage(page, masks = [], mode = 'viewport') {
|
|
12
|
+
// `fullPage` composites the whole scrollable document into one image. When
|
|
13
|
+
// the document is captured at a non-zero offset, `position: sticky` / `fixed`
|
|
14
|
+
// chrome (sidebars, headers) resolves against that scroll position and lands
|
|
15
|
+
// shifted down by `scrollY` in the stitch — the same UI produces a different
|
|
16
|
+
// image purely because an earlier step left the viewport scrolled. Resetting
|
|
17
|
+
// to the origin first pins those elements to their static-baseline position
|
|
18
|
+
// so the capture is deterministic. `instant` defeats any `scroll-behavior:
|
|
19
|
+
// smooth` the page sets, which would otherwise animate and reintroduce a
|
|
20
|
+
// timing race.
|
|
21
|
+
//
|
|
22
|
+
// A `viewport` shot captures exactly the box the flow scrolled to, so it must
|
|
23
|
+
// NOT reset — a story that scrolls to a below-the-fold section (a Settings
|
|
24
|
+
// panel, a success banner) would otherwise capture the top of the page
|
|
25
|
+
// instead of the region it navigated to. The flow lands at a deterministic
|
|
26
|
+
// offset, so the viewport shot is deterministic without any reset.
|
|
27
|
+
//
|
|
28
|
+
// String form so the snippet runs in the page (DOM) context without pulling
|
|
29
|
+
// the DOM lib into this Node-side module's type environment.
|
|
30
|
+
if (mode === 'fullPage') {
|
|
31
|
+
await page.evaluate(`window.scrollTo({ top: 0, left: 0, behavior: 'instant' })`);
|
|
32
|
+
}
|
|
20
33
|
return page.screenshot({
|
|
21
|
-
fullPage:
|
|
34
|
+
fullPage: mode === 'fullPage',
|
|
22
35
|
animations: 'disabled',
|
|
23
36
|
caret: 'hide',
|
|
24
37
|
mask: masks,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/screenshots/capture.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/screenshots/capture.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAU,EACV,QAAmB,EAAE,EACrB,OAAoB,UAAU;IAE9B,2EAA2E;IAC3E,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,eAAe;IACf,EAAE;IACF,8EAA8E;IAC9E,2EAA2E;IAC3E,uEAAuE;IACvE,2EAA2E;IAC3E,mEAAmE;IACnE,EAAE;IACF,4EAA4E;IAC5E,6DAA6D;IAC7D,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,MAAM,IAAI,CAAC,QAAQ,CACjB,2DAA2D,CAC5D,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,UAAU,CAAC;QACrB,QAAQ,EAAE,IAAI,KAAK,UAAU;QAC7B,UAAU,EAAE,UAAU;QACtB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC"}
|