styleproof 4.3.0 → 4.4.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/CHANGELOG.md +22 -0
- package/README.md +1 -0
- package/bin/styleproof-map.mjs +3 -1
- package/dist/runner.d.ts +12 -0
- package/dist/runner.js +21 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.4.0] - 2026-07-13
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Spec-driven captures now run in parallel across Playwright workers.** The
|
|
15
|
+
capture describe declares `parallel` mode itself, so surface×width tests fan
|
|
16
|
+
out over the consumer's `workers` even when the project pins
|
|
17
|
+
`fullyParallel: false` for its behaviour suite. Every generated test was
|
|
18
|
+
already independent (own page, own map/HAR files, own self-check; ledger and
|
|
19
|
+
manifest tests mkdir and tolerate any order), so the maps are byte-identical —
|
|
20
|
+
a real consumer's 150-capture run dropped from 24.5 to 6.0 minutes at 4
|
|
21
|
+
workers on the same box. A spec file whose OWN sibling tests read the captured
|
|
22
|
+
maps in file order can opt out with `parallel: false`. Found while
|
|
23
|
+
dogfooding against a large single-route consumer app.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **An absolute `STYLEMAP_DIR` (or `--dir`) is respected as-is.** It was joined
|
|
28
|
+
under `baseDir`, so `STYLEMAP_DIR=/abs/path styleproof-map` stranded the maps
|
|
29
|
+
at `.styleproof/maps/abs/path` where no consumer looks. Relative dirs nest
|
|
30
|
+
under `baseDir` unchanged. Also found dogfooding against a consumer app.
|
|
31
|
+
|
|
10
32
|
## [4.3.0] - 2026-07-13
|
|
11
33
|
|
|
12
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -986,6 +986,7 @@ It's **asynchronous by design**: approval is a checkbox tick handled by a separa
|
|
|
986
986
|
| `dataResidue` | `'gate'` | Name data-boundary (`replayUrl`) requests that **fail** during capture (network error / 4xx/5xx — the fallback branch got captured). Always warned + recorded; `'gate'` (the default) also blocks the diff on an unacknowledged one, `'warn'` is the opt-out that records + warns without gating. See [Data residue](#data-residue-a-failed-data-request-is-named-not-swallowed). |
|
|
987
987
|
| `freezeClock` | `true` | Pin `Date.now()`/`new Date()` so time-derived styling can't drift; timers keep running so settling still works. |
|
|
988
988
|
| `clockTime` | `2025-01-01T00:00:00Z` | The frozen instant. |
|
|
989
|
+
| `parallel` | `true` | Run the generated capture tests across Playwright workers, even when the project config pins `fullyParallel: false` — every capture test is independent, so a multi-surface spec speeds up ~workers×. Set `false` only for a spec file whose own sibling tests read the captured maps in file order. |
|
|
989
990
|
| `selfCheck` | on while recording | Capture each surface twice and fail on any difference — proves the capture is deterministic. Off on the replay run; `STYLEPROOF_SELFCHECK=1` forces both. |
|
|
990
991
|
| `screenshots` | `true` | Save full-page screenshots for the report's before/after crops. |
|
|
991
992
|
| `baseDir` | `__stylemaps__` | Output root directory. |
|
package/bin/styleproof-map.mjs
CHANGED
|
@@ -253,7 +253,9 @@ function runVariantCrawl(env) {
|
|
|
253
253
|
if (status !== 0) process.exit(status);
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
// An ABSOLUTE STYLEMAP_DIR/--dir is respected as-is; a relative one nests under
|
|
257
|
+
// baseDir (.styleproof/maps by default) — mirrors the runner's resolveOutputDir.
|
|
258
|
+
const targetDir = path.isAbsolute(dir) ? dir : path.join(baseDir, dir);
|
|
257
259
|
// Sample the tree state the capture is ABOUT to render, so the manifest can bind the
|
|
258
260
|
// map to it. A capture runs for minutes; if the source is edited or HEAD moves in that
|
|
259
261
|
// window, the map renders one state but would otherwise be stamped clean@post-HEAD and
|
package/dist/runner.d.ts
CHANGED
|
@@ -150,6 +150,14 @@ export type DefineOptions = {
|
|
|
150
150
|
* `selfCheck` explicitly to override.
|
|
151
151
|
*/
|
|
152
152
|
selfCheck?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Run the generated capture tests in PARALLEL across Playwright workers
|
|
155
|
+
* (default true). Every capture test is independent, so parallel is safe and
|
|
156
|
+
* ~workers× faster on a multi-surface spec — even when the project config
|
|
157
|
+
* pins `fullyParallel: false`. Set false ONLY for a spec file whose OTHER
|
|
158
|
+
* tests read the captured maps in file order (an in-file assertion suite).
|
|
159
|
+
*/
|
|
160
|
+
parallel?: boolean;
|
|
153
161
|
/**
|
|
154
162
|
* Opt-in content layer (default OFF). Record each element's own rendered text
|
|
155
163
|
* so the report's optional content section can surface copy changes (run
|
|
@@ -239,6 +247,10 @@ export declare function passLiveStreams(page: Page, url: string): Promise<void>;
|
|
|
239
247
|
* forces it on either way.
|
|
240
248
|
*/
|
|
241
249
|
export declare function defaultSelfCheck(replayFrom: string | undefined, env?: string | undefined): boolean;
|
|
250
|
+
/** Resolve a capture output dir: an ABSOLUTE `dir` is respected as-is (a user's
|
|
251
|
+
* `STYLEMAP_DIR=/abs/path` must not be buried under `baseDir`); a relative one
|
|
252
|
+
* nests under `baseDir` as before. */
|
|
253
|
+
export declare function resolveOutputDir(baseDir: string, dir: string): string;
|
|
242
254
|
/**
|
|
243
255
|
* Output base dir: explicit `baseDir` wins, then `STYLEPROOF_BASEDIR`, then the
|
|
244
256
|
* default. Lets CLIs and CI redirect capture into cache/fallback dirs without
|
package/dist/runner.js
CHANGED
|
@@ -304,7 +304,7 @@ async function pinInputs(page, harName, s) {
|
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
else {
|
|
307
|
-
await page.routeFromHAR(path.join(s.baseDir, s.dir, harName), {
|
|
307
|
+
await page.routeFromHAR(path.join(resolveOutputDir(s.baseDir, s.dir), harName), {
|
|
308
308
|
url: s.replayUrl,
|
|
309
309
|
update: true,
|
|
310
310
|
updateContent: 'embed', // single portable file, no sidecar resources
|
|
@@ -443,7 +443,7 @@ async function capturePopupCandidate(page, surface, width, height, s, options, c
|
|
|
443
443
|
return;
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
|
-
const stem = path.join(s.baseDir, s.dir, `${surface.key}-${popupId}@${width}`);
|
|
446
|
+
const stem = path.join(resolveOutputDir(s.baseDir, s.dir), `${surface.key}-${popupId}@${width}`);
|
|
447
447
|
saveStyleMap(`${stem}.json.gz`, map);
|
|
448
448
|
if (s.screenshots)
|
|
449
449
|
await page.screenshot({ path: `${stem}.png`, fullPage: true, animations: 'disabled' });
|
|
@@ -495,7 +495,7 @@ async function captureSurface(page, surface, width, s) {
|
|
|
495
495
|
// Attach data-residue AFTER the self-check re-run so both runs' failures are folded
|
|
496
496
|
// (deduped in the watcher). Warn always; the recorded residue is what the gate reads.
|
|
497
497
|
attachDataResidue(map, residue.residue());
|
|
498
|
-
const stem = path.join(s.baseDir, s.dir, `${surface.key}@${width}`);
|
|
498
|
+
const stem = path.join(resolveOutputDir(s.baseDir, s.dir), `${surface.key}@${width}`);
|
|
499
499
|
saveStyleMap(`${stem}.json.gz`, map);
|
|
500
500
|
if (s.screenshots) {
|
|
501
501
|
// captureStyleMap froze animations/transitions, so this is the same settled
|
|
@@ -533,6 +533,12 @@ function attachDataResidue(map, residue) {
|
|
|
533
533
|
export function defaultSelfCheck(replayFrom, env = process.env.STYLEPROOF_SELFCHECK) {
|
|
534
534
|
return env === '1' || !replayFrom;
|
|
535
535
|
}
|
|
536
|
+
/** Resolve a capture output dir: an ABSOLUTE `dir` is respected as-is (a user's
|
|
537
|
+
* `STYLEMAP_DIR=/abs/path` must not be buried under `baseDir`); a relative one
|
|
538
|
+
* nests under `baseDir` as before. */
|
|
539
|
+
export function resolveOutputDir(baseDir, dir) {
|
|
540
|
+
return path.isAbsolute(dir) ? dir : path.join(baseDir, dir);
|
|
541
|
+
}
|
|
536
542
|
/**
|
|
537
543
|
* Output base dir: explicit `baseDir` wins, then `STYLEPROOF_BASEDIR`, then the
|
|
538
544
|
* default. Lets CLIs and CI redirect capture into cache/fallback dirs without
|
|
@@ -602,7 +608,7 @@ function resolveSettings(c) {
|
|
|
602
608
|
*/
|
|
603
609
|
function writeCoverageLedgerTest(settings, dir, expected, exclude, captureSurfaces) {
|
|
604
610
|
test('styleproof coverage ledger', () => {
|
|
605
|
-
const outDir =
|
|
611
|
+
const outDir = resolveOutputDir(settings.baseDir, dir);
|
|
606
612
|
fs.mkdirSync(outDir, { recursive: true });
|
|
607
613
|
// Determinism basis: self-check ON proves it (a drift would have failed the capture);
|
|
608
614
|
// else a replay run is deterministic by construction; else it's unproven.
|
|
@@ -648,7 +654,7 @@ function writeCoverageLedgerTest(settings, dir, expected, exclude, captureSurfac
|
|
|
648
654
|
function writeBrowserBuildTest(settings, dir) {
|
|
649
655
|
test('styleproof browser build', ({ page }) => {
|
|
650
656
|
const version = page.context().browser()?.version();
|
|
651
|
-
const outDir =
|
|
657
|
+
const outDir = resolveOutputDir(settings.baseDir, dir);
|
|
652
658
|
writeBrowserBuildSidecar(outDir, version);
|
|
653
659
|
writeCaptureManifest({ dir: outDir, screenshots: settings.screenshots });
|
|
654
660
|
});
|
|
@@ -681,6 +687,16 @@ export function defineStyleMapCapture(options) {
|
|
|
681
687
|
return;
|
|
682
688
|
const settings = resolveSettings(options);
|
|
683
689
|
test.describe('styleproof capture', () => {
|
|
690
|
+
// Every generated test is independent — its own page, its own map/HAR files,
|
|
691
|
+
// its own self-check; the ledger/manifest tests mkdir and tolerate any order.
|
|
692
|
+
// Declare the block PARALLEL so captures fan out across the consumer's
|
|
693
|
+
// Playwright workers even when the project pins `fullyParallel: false` for
|
|
694
|
+
// its behaviour suite (150 serial surface×width captures is a ~25-minute CI
|
|
695
|
+
// step; 4 workers make it ~4x faster with byte-identical maps).
|
|
696
|
+
// `parallel: false` keeps file order for specs whose own sibling tests read
|
|
697
|
+
// the captured maps.
|
|
698
|
+
if (options.parallel !== false)
|
|
699
|
+
test.describe.configure({ mode: 'parallel' });
|
|
684
700
|
writeCoverageLedgerTest(settings, dir, expected ?? null, exclude, captureSurfaces);
|
|
685
701
|
writeBrowserBuildTest(settings, dir);
|
|
686
702
|
for (const surface of captureSurfaces) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styleproof",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Catch every CSS change before it ships — review PRs and certify refactors by the browser's computed styles, not pixels. Works with any styling system.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"playwright",
|