snapdrift 0.11.0 → 0.12.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 +47 -0
- package/README.md +2 -2
- package/action.yml +2 -2
- package/docs/contracts.md +28 -8
- package/docs/integration-guide.md +9 -11
- package/docs/workflow-templates/refresh-baseline-on-merge.yml +1 -1
- package/lib/snap-provider.mjs +1 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.0 - 2026-09-16
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **PR comments no longer render `[object Object]` for a viewport.** A viewport is
|
|
8
|
+
either a preset name or a `{ width, height }` object, and the PR-comment renderer
|
|
9
|
+
stringified it directly. Hosted diff runs always build the object form, so every
|
|
10
|
+
hosted report printed the useless placeholder in its Error, Drift signals, and
|
|
11
|
+
Dimension shifts tables. All three report renderers now share one
|
|
12
|
+
`formatViewport` helper (exported from `@snapdrift/adapter-report-md`).
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **`compareImages` accepts a per-call `maxPixels` union-canvas ceiling.**
|
|
17
|
+
`MAX_COMPARISON_PIXELS` (32 Mi) is a *process memory* bound, not an algorithm
|
|
18
|
+
property, so callers with a larger envelope — a dedicated diff Lambda — can raise
|
|
19
|
+
it while callers sharing a small host keep the default. A missing, non-positive,
|
|
20
|
+
or non-finite value falls back to the default, so the guard cannot be removed by
|
|
21
|
+
accident. The `ComparisonTooLargeError` message now reports the ceiling that was
|
|
22
|
+
actually exceeded.
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
- Document that `migrate-baselines --to snap` is unsupported and point to
|
|
27
|
+
`snapdrift baseline` as the supported way to seed a hosted project. The
|
|
28
|
+
integration guide still described the retired upload direction as a working
|
|
29
|
+
one-shot command, and the contracts reference documented its request body,
|
|
30
|
+
idempotency, and storage behavior as if it succeeded. Both contradicted the
|
|
31
|
+
CLI, which has failed the direction since 0.7.0 with a pointer to
|
|
32
|
+
`snapdrift baseline`.
|
|
33
|
+
- Document the screenshot size budget's memory cost for `maxPixels` callers: the
|
|
34
|
+
comparison retains both decoded RGBA inputs plus the optional diff canvas, so the
|
|
35
|
+
floor is roughly 12 bytes per union pixel before PNG overhead (~32-35 measured end
|
|
36
|
+
to end), and the ceiling should be sized against a measured peak rather than that
|
|
37
|
+
arithmetic.
|
|
38
|
+
|
|
39
|
+
### Workspace packages
|
|
40
|
+
|
|
41
|
+
- `@snapdrift/adapter-report-md` 1.2.1 publishes the `[object Object]` viewport fix;
|
|
42
|
+
`@snapdrift/compare-core` 1.3.0 publishes the per-call `maxPixels` option and the
|
|
43
|
+
new size-budget guidance.
|
|
44
|
+
- The root package's dependency floors move to `^1.2.1` (adapter-report-md) and
|
|
45
|
+
`^1.3.0` (compare-core) so `snapdrift` 0.12.0 actually requires those fixes. A
|
|
46
|
+
retained lockfile resolution of adapter-report-md 1.2.0 still satisfies `^1.2.0`,
|
|
47
|
+
so without the floor bump an upgrade of `snapdrift` alone could install the
|
|
48
|
+
unfixed report renderer.
|
|
49
|
+
|
|
3
50
|
## 0.11.0 - 2026-09-14
|
|
4
51
|
|
|
5
52
|
### Behavior changes
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ You keep ownership of checkout, build, startup, readiness, and teardown. SnapDri
|
|
|
48
48
|
|
|
49
49
|
```yaml
|
|
50
50
|
- name: SnapDrift Baseline
|
|
51
|
-
uses: ranacseruet/snapdrift@v0.
|
|
51
|
+
uses: ranacseruet/snapdrift@v0.12.0
|
|
52
52
|
with:
|
|
53
53
|
mode: baseline
|
|
54
54
|
repo-config-path: .github/snapdrift.json
|
|
@@ -60,7 +60,7 @@ With `provider: "snap"`, baseline publication is a complete, fail-closed snapsho
|
|
|
60
60
|
|
|
61
61
|
```yaml
|
|
62
62
|
- name: SnapDrift Report
|
|
63
|
-
uses: ranacseruet/snapdrift@v0.
|
|
63
|
+
uses: ranacseruet/snapdrift@v0.12.0
|
|
64
64
|
with:
|
|
65
65
|
mode: pr-diff
|
|
66
66
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
package/action.yml
CHANGED
|
@@ -155,7 +155,7 @@ runs:
|
|
|
155
155
|
- name: SnapDrift baseline
|
|
156
156
|
id: baseline
|
|
157
157
|
if: inputs.mode == 'baseline'
|
|
158
|
-
uses: ranacseruet/snapdrift/actions/baseline@
|
|
158
|
+
uses: ranacseruet/snapdrift/actions/baseline@e4b92c2243c6e69594cab7405f68eb58fe94587e # v0.12.0
|
|
159
159
|
with:
|
|
160
160
|
repo-config-path: ${{ inputs.repo-config-path }}
|
|
161
161
|
route-ids: ${{ inputs.route-ids }}
|
|
@@ -165,7 +165,7 @@ runs:
|
|
|
165
165
|
- name: SnapDrift pull request diff
|
|
166
166
|
id: pr-diff
|
|
167
167
|
if: inputs.mode == 'pr-diff'
|
|
168
|
-
uses: ranacseruet/snapdrift/actions/pr-diff@
|
|
168
|
+
uses: ranacseruet/snapdrift/actions/pr-diff@e4b92c2243c6e69594cab7405f68eb58fe94587e # v0.12.0
|
|
169
169
|
with:
|
|
170
170
|
github-token: ${{ inputs.github-token }}
|
|
171
171
|
repo-config-path: ${{ inputs.repo-config-path }}
|
package/docs/contracts.md
CHANGED
|
@@ -297,7 +297,29 @@ dimensions.
|
|
|
297
297
|
|
|
298
298
|
The comparator reports this condition with the stable error code
|
|
299
299
|
`comparison_too_large` and includes the baseline, current, and union-canvas
|
|
300
|
-
dimensions in the error message.
|
|
300
|
+
dimensions in the error message. The ceiling that was exceeded is included too,
|
|
301
|
+
since a caller may raise it per call.
|
|
302
|
+
|
|
303
|
+
`32 × 1024 × 1024` is the *default* ceiling, not an algorithm property. The cost
|
|
304
|
+
is dominated by the decoded images: both RGBA inputs (4 bytes per pixel each) are
|
|
305
|
+
retained plus, when the diff image is rendered (the default), a union-sized diff
|
|
306
|
+
canvas — at least ~12 bytes per union pixel when both inputs approach the union
|
|
307
|
+
dimensions, before PNG decode/encode overhead. Measured end-to-end RSS on real
|
|
308
|
+
full-page captures is ~32-35 bytes per union pixel (~1.16 GB at 33.8 M pixels,
|
|
309
|
+
~3.37 GB at 103.9 M), so the default bounds a comparison to a process with a few
|
|
310
|
+
GB of headroom.
|
|
311
|
+
|
|
312
|
+
Direct `@snapdrift/compare-core` callers running in a larger memory envelope can
|
|
313
|
+
pass `compareImages(..., { maxPixels })` to raise it for that call. **Size
|
|
314
|
+
`maxPixels` against your own measured peak**, not against the byte-per-pixel
|
|
315
|
+
arithmetic: PNG decode and encode overhead, GC, and concurrency all add to it. A
|
|
316
|
+
missing, non-finite, or sub-1 `maxPixels` falls back to the default; fractions are
|
|
317
|
+
floored (`16.9` becomes `16`). The sub-1 rule matters because a positive fraction
|
|
318
|
+
such as `0.5` floors to `0`, which would otherwise reject every comparison.
|
|
319
|
+
SnapDrift's own local and hosted paths keep the default, because the host process
|
|
320
|
+
— not the image — decides how much memory is actually safe.
|
|
321
|
+
|
|
322
|
+
Approximate maximum full-page CSS heights below
|
|
301
323
|
assume that the raster width remains equal to the configured CSS width and that
|
|
302
324
|
the document height scales by the same device scale factor:
|
|
303
325
|
|
|
@@ -353,18 +375,16 @@ All three directories can be overridden with `--baseline-dir`, `--current-dir`,
|
|
|
353
375
|
|
|
354
376
|
### migrate-baselines
|
|
355
377
|
|
|
356
|
-
|
|
378
|
+
Move baselines between local storage and Snap. Only the `snap → local` direction is supported; it requires a `snap` block in `snapdrift.json` (the export call talks to Snap).
|
|
357
379
|
|
|
358
|
-
**Upload local baselines to Snap:**
|
|
380
|
+
**Upload local baselines to Snap — not supported:**
|
|
359
381
|
|
|
360
382
|
```
|
|
361
|
-
snapdrift migrate-baselines --to snap
|
|
383
|
+
snapdrift migrate-baselines --to snap # always fails
|
|
362
384
|
```
|
|
363
385
|
|
|
364
|
-
-
|
|
365
|
-
-
|
|
366
|
-
- Idempotent: if a baseline already exists for the same commit SHA (derived from `GITHUB_SHA` or `git rev-parse HEAD`), the upload is skipped.
|
|
367
|
-
- Screenshots are base64-encoded in the request body; very large suites may want to migrate per-route.
|
|
386
|
+
- The command exits with an error. Snap cannot accept a pre-built local baseline bundle: the screenshots it carries are never uploaded to Snap storage, and its manifest references local filenames rather than Snap object keys, so the call could only ever create a baseline with no pixels behind it. Snap rejects the request with `400 unsupported_baseline_body` (i2Dev-com/snap#653); the legacy `SnapProvider.migrateBaselineFromLocal()` path was removed in 0.7.0.
|
|
387
|
+
- **Use `snapdrift baseline` instead** — with `provider: "snap"` it captures each route through Snap so the images actually land in storage, then publishes a baseline referencing the stored objects. Canonical hosted publication is CI-only (default-branch job); see [`snapdrift baseline`](local-cli.md#snapdrift-baseline).
|
|
368
388
|
|
|
369
389
|
**Download Snap baselines to local:**
|
|
370
390
|
|
|
@@ -46,7 +46,7 @@ SnapDrift owns route selection, capture, comparison, skipped-report generation,
|
|
|
46
46
|
|
|
47
47
|
```yaml
|
|
48
48
|
- name: SnapDrift Baseline
|
|
49
|
-
uses: ranacseruet/snapdrift@v0.
|
|
49
|
+
uses: ranacseruet/snapdrift@v0.12.0
|
|
50
50
|
with:
|
|
51
51
|
mode: baseline
|
|
52
52
|
repo-config-path: .github/snapdrift.json
|
|
@@ -71,7 +71,7 @@ Then add SnapDrift after the app is running:
|
|
|
71
71
|
|
|
72
72
|
```yaml
|
|
73
73
|
- name: SnapDrift Report
|
|
74
|
-
uses: ranacseruet/snapdrift@v0.
|
|
74
|
+
uses: ranacseruet/snapdrift@v0.12.0
|
|
75
75
|
with:
|
|
76
76
|
mode: pr-diff
|
|
77
77
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -86,8 +86,8 @@ The wrappers remain published in their own right, and are equivalent to the disp
|
|
|
86
86
|
|
|
87
87
|
| Entry point | Equivalent to |
|
|
88
88
|
|-------------|---------------|
|
|
89
|
-
| `ranacseruet/snapdrift@v0.
|
|
90
|
-
| `ranacseruet/snapdrift@v0.
|
|
89
|
+
| `ranacseruet/snapdrift@v0.12.0` with `mode: baseline` | `ranacseruet/snapdrift/actions/baseline@v0.12.0` |
|
|
90
|
+
| `ranacseruet/snapdrift@v0.12.0` with `mode: pr-diff` | `ranacseruet/snapdrift/actions/pr-diff@v0.12.0` |
|
|
91
91
|
|
|
92
92
|
Use the wrappers directly when you want to skip the dispatcher's input indirection, and the lower-level `capture`, `compare`, `scope`, `resolve-baseline`, `stage`, `comment`, and `enforce` actions when you need to orchestrate the stages yourself.
|
|
93
93
|
|
|
@@ -130,7 +130,7 @@ jobs:
|
|
|
130
130
|
done
|
|
131
131
|
|
|
132
132
|
- name: SnapDrift Report
|
|
133
|
-
uses: ranacseruet/snapdrift@v0.
|
|
133
|
+
uses: ranacseruet/snapdrift@v0.12.0
|
|
134
134
|
with:
|
|
135
135
|
mode: pr-diff
|
|
136
136
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -170,7 +170,7 @@ jobs:
|
|
|
170
170
|
done
|
|
171
171
|
|
|
172
172
|
- name: SnapDrift Report
|
|
173
|
-
uses: ranacseruet/snapdrift@v0.
|
|
173
|
+
uses: ranacseruet/snapdrift@v0.12.0
|
|
174
174
|
with:
|
|
175
175
|
mode: pr-diff
|
|
176
176
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -249,10 +249,11 @@ When `provider: "snap"` and `baseUrl` points to a local address (localhost, `127
|
|
|
249
249
|
|
|
250
250
|
With the Snap provider, the PR comment includes a **"View in dashboard →"** link that points to `${apiUrl}/dashboard/visual/${projectId}/runs/${runId}`. The local provider omits the link.
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
To adopt the hosted provider, seed the project with a hosted baseline. `migrate-baselines --to snap` is **not** the way to do it — Snap cannot accept a pre-built local baseline bundle, because the screenshots it carries are never uploaded to Snap storage and its manifest references local filenames rather than Snap object keys. Use `snapdrift baseline` with `provider: "snap"` instead: it captures each route through Snap so the pixels actually land in storage before the baseline manifest is published. Canonical hosted publication is CI-only — run it from your default-branch job, where GitHub Actions supplies the publication metadata automatically (other CI systems set `SNAPDRIFT_PUBLICATION_WORKFLOW_REF` and `SNAPDRIFT_PUBLICATION_SEQUENCE`); a plain local checkout fails before creating a hosted run.
|
|
253
253
|
|
|
254
254
|
```bash
|
|
255
|
-
|
|
255
|
+
# In CI, on the default branch, with provider: "snap"
|
|
256
|
+
snapdrift baseline
|
|
256
257
|
```
|
|
257
258
|
|
|
258
259
|
Conversely, downloading a Snap baseline back to a local directory (useful for reproducible local debugging) is:
|
|
@@ -297,9 +298,6 @@ snapdrift capture
|
|
|
297
298
|
# After making changes, compare and open the HTML report
|
|
298
299
|
snapdrift diff --open
|
|
299
300
|
|
|
300
|
-
# Migrate an established local baseline to the hosted Snap backend
|
|
301
|
-
snapdrift migrate-baselines --to snap
|
|
302
|
-
|
|
303
301
|
# Translate a snap/github-action workflow into snapdrift.json
|
|
304
302
|
snapdrift init --from-snap-action .github/workflows/snap.yml
|
|
305
303
|
```
|
package/lib/snap-provider.mjs
CHANGED
|
@@ -46,7 +46,6 @@ const BINARY_REQUEST_TIMEOUT_MS = 120 * 1000;
|
|
|
46
46
|
const POLL_INTERVAL_MS = 2000;
|
|
47
47
|
const MAX_OPERATION_DURATION_MS = 10 * 60 * 1000;
|
|
48
48
|
const MAX_STABLE_INCOMPLETE_POLLS = 30;
|
|
49
|
-
const MAX_COMPARISON_PIXELS = 32 * 1024 * 1024;
|
|
50
49
|
const BASELINE_RUN_SUCCESS_STATUS = 'new';
|
|
51
50
|
const BASELINE_CAPTURE_SUCCESS_STATUS = 'new';
|
|
52
51
|
const TERMINAL_CAPTURE_STATUSES = new Set(['new', 'diffed', 'error']);
|
|
@@ -178,8 +177,7 @@ function isComparisonMetadata(value) {
|
|
|
178
177
|
canvas.height === Math.max(baseline.height, current.height) &&
|
|
179
178
|
Number.isInteger(comparison.totalPixels) &&
|
|
180
179
|
comparison.totalPixels >= 0 &&
|
|
181
|
-
comparison.totalPixels <= canvas.width * canvas.height
|
|
182
|
-
canvas.width * canvas.height <= MAX_COMPARISON_PIXELS
|
|
180
|
+
comparison.totalPixels <= canvas.width * canvas.height
|
|
183
181
|
);
|
|
184
182
|
}
|
|
185
183
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snapdrift",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "SnapDrift captures, compares, and reports UI drift for Playwright-based app checks.",
|
|
@@ -103,8 +103,8 @@
|
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
105
|
"@snapdrift/adapter-fs": "^1.3.0",
|
|
106
|
-
"@snapdrift/adapter-report-md": "^1.2.
|
|
107
|
-
"@snapdrift/compare-core": "^1.
|
|
106
|
+
"@snapdrift/adapter-report-md": "^1.2.1",
|
|
107
|
+
"@snapdrift/compare-core": "^1.3.0",
|
|
108
108
|
"@snapdrift/manifest": "^1.4.0",
|
|
109
109
|
"js-yaml": "^5.4.0",
|
|
110
110
|
"playwright": "^1.59.1",
|