snapdrift 0.2.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 +43 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/actions/baseline/action.yml +142 -0
- package/actions/capture/action.yml +78 -0
- package/actions/comment/action.yml +104 -0
- package/actions/compare/action.yml +163 -0
- package/actions/enforce/action.yml +50 -0
- package/actions/pr-diff/action.yml +612 -0
- package/actions/resolve-baseline/action.yml +165 -0
- package/actions/scope/action.yml +107 -0
- package/actions/stage/action.yml +116 -0
- package/bin/snapdrift.mjs +14 -0
- package/docs/contracts.md +233 -0
- package/docs/integration-guide.md +222 -0
- package/docs/local-cli.md +174 -0
- package/lib/capture-routes.mjs +370 -0
- package/lib/cli.mjs +241 -0
- package/lib/compare-results.mjs +643 -0
- package/lib/drift-summary.mjs +156 -0
- package/lib/pr-comment.mjs +147 -0
- package/lib/report.mjs +268 -0
- package/lib/snapdrift-config.mjs +280 -0
- package/lib/stage-artifacts.mjs +125 -0
- package/package.json +94 -0
- package/types/visual-diff-types.d.ts +137 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide covers integrating SnapDrift into a consumer repository from a public GitHub release.
|
|
4
|
+
|
|
5
|
+
Examples use the public release tag for readability. If your organization requires immutable pins, resolve the tag to a commit SHA and pin that instead.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- SnapDrift provisions Node 22 internally.
|
|
10
|
+
- The consumer app can use any stack as long as it can be started and reached at `baseUrl`.
|
|
11
|
+
- The runner must be Ubuntu-compatible because Playwright Chromium is installed with system dependencies.
|
|
12
|
+
- The runner must have outbound network access to fetch Playwright Chromium.
|
|
13
|
+
|
|
14
|
+
Your repository still owns:
|
|
15
|
+
|
|
16
|
+
- Checkout
|
|
17
|
+
- Dependency installation
|
|
18
|
+
- App build
|
|
19
|
+
- App startup and readiness
|
|
20
|
+
- App shutdown
|
|
21
|
+
|
|
22
|
+
SnapDrift owns route selection, capture, comparison, skipped-report generation, artifact staging, artifact upload, PR reporting, and drift enforcement.
|
|
23
|
+
|
|
24
|
+
## Step 1: Add `.github/snapdrift.json`
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"baselineArtifactName": "my-app-snapdrift-baseline",
|
|
29
|
+
"workingDirectory": ".",
|
|
30
|
+
"baseUrl": "http://127.0.0.1:8080",
|
|
31
|
+
"resultsFile": "qa-artifacts/snapdrift/baseline/current/results.json",
|
|
32
|
+
"manifestFile": "qa-artifacts/snapdrift/baseline/current/manifest.json",
|
|
33
|
+
"screenshotsRoot": "qa-artifacts/snapdrift/baseline/current",
|
|
34
|
+
"routes": [
|
|
35
|
+
{ "id": "home-desktop", "path": "/", "viewport": "desktop" },
|
|
36
|
+
{ "id": "home-mobile", "path": "/", "viewport": "mobile" }
|
|
37
|
+
],
|
|
38
|
+
"diff": {
|
|
39
|
+
"threshold": 0.01,
|
|
40
|
+
"mode": "report-only"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Step 2: Publish a baseline on `main`
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
- name: SnapDrift Baseline
|
|
49
|
+
uses: ranacseruet/snapdrift/actions/baseline@v0.1.0
|
|
50
|
+
with:
|
|
51
|
+
repo-config-path: .github/snapdrift.json
|
|
52
|
+
artifact-retention-days: '30'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you want only part of the route set, pass `route-ids`.
|
|
56
|
+
|
|
57
|
+
## Step 3: Run the pull request report
|
|
58
|
+
|
|
59
|
+
The PR workflow needs permission to download baselines and upsert the report comment:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
permissions:
|
|
63
|
+
contents: read
|
|
64
|
+
actions: read
|
|
65
|
+
issues: write
|
|
66
|
+
pull-requests: write
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then add SnapDrift after the app is running:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
- name: SnapDrift Report
|
|
73
|
+
uses: ranacseruet/snapdrift/actions/pr-diff@v0.1.0
|
|
74
|
+
with:
|
|
75
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
76
|
+
repo-config-path: .github/snapdrift.json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Example workflow (Node app)
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
name: SnapDrift Pull Request
|
|
83
|
+
|
|
84
|
+
on:
|
|
85
|
+
pull_request:
|
|
86
|
+
branches: [main]
|
|
87
|
+
|
|
88
|
+
permissions:
|
|
89
|
+
contents: read
|
|
90
|
+
actions: read
|
|
91
|
+
issues: write
|
|
92
|
+
pull-requests: write
|
|
93
|
+
|
|
94
|
+
jobs:
|
|
95
|
+
snapdrift:
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v4
|
|
99
|
+
- uses: actions/setup-node@v4
|
|
100
|
+
with:
|
|
101
|
+
node-version: '22'
|
|
102
|
+
cache: npm
|
|
103
|
+
- run: npm ci
|
|
104
|
+
- run: npm run build
|
|
105
|
+
|
|
106
|
+
- name: Start app
|
|
107
|
+
run: |
|
|
108
|
+
npm start &
|
|
109
|
+
for i in $(seq 1 45); do
|
|
110
|
+
curl -sf http://127.0.0.1:8080 && break || sleep 1
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
- name: SnapDrift Report
|
|
114
|
+
uses: ranacseruet/snapdrift/actions/pr-diff@v0.1.0
|
|
115
|
+
with:
|
|
116
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
117
|
+
repo-config-path: .github/snapdrift.json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Example workflow (Python app)
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
name: SnapDrift Pull Request
|
|
124
|
+
|
|
125
|
+
on:
|
|
126
|
+
pull_request:
|
|
127
|
+
branches: [main]
|
|
128
|
+
|
|
129
|
+
permissions:
|
|
130
|
+
contents: read
|
|
131
|
+
actions: read
|
|
132
|
+
issues: write
|
|
133
|
+
pull-requests: write
|
|
134
|
+
|
|
135
|
+
jobs:
|
|
136
|
+
snapdrift:
|
|
137
|
+
runs-on: ubuntu-latest
|
|
138
|
+
steps:
|
|
139
|
+
- uses: actions/checkout@v4
|
|
140
|
+
- uses: actions/setup-python@v5
|
|
141
|
+
with:
|
|
142
|
+
python-version: '3.12'
|
|
143
|
+
- run: pip install -r requirements.txt
|
|
144
|
+
|
|
145
|
+
- name: Start app
|
|
146
|
+
run: |
|
|
147
|
+
gunicorn myapp:app --bind 127.0.0.1:8080 &
|
|
148
|
+
for i in $(seq 1 45); do
|
|
149
|
+
curl -sf http://127.0.0.1:8080 && break || sleep 1
|
|
150
|
+
done
|
|
151
|
+
|
|
152
|
+
- name: SnapDrift Report
|
|
153
|
+
uses: ranacseruet/snapdrift/actions/pr-diff@v0.1.0
|
|
154
|
+
with:
|
|
155
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
156
|
+
repo-config-path: .github/snapdrift.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## What the wrapper does
|
|
160
|
+
|
|
161
|
+
- Resolves route scope unless `route-ids` is passed explicitly
|
|
162
|
+
- Downloads the latest successful `main` baseline
|
|
163
|
+
- Captures current frames
|
|
164
|
+
- Writes a skipped report when scope is irrelevant or the baseline is missing
|
|
165
|
+
- Stages and uploads the drift artifact
|
|
166
|
+
- Publishes the workflow summary
|
|
167
|
+
- Upserts the PR report
|
|
168
|
+
- Enforces `diff.mode` after publication completes
|
|
169
|
+
|
|
170
|
+
## Useful overrides
|
|
171
|
+
|
|
172
|
+
| Input | Purpose |
|
|
173
|
+
|-------|---------|
|
|
174
|
+
| `pr-number` | Explicit PR number override |
|
|
175
|
+
| `route-ids` | Bypass scope detection and capture named routes |
|
|
176
|
+
| `force-run` | Run the full route set |
|
|
177
|
+
| `baseline-repository` | Read baselines from another repo |
|
|
178
|
+
| `baseline-workflow-id` | Override the baseline workflow id |
|
|
179
|
+
| `baseline-branch` | Override the baseline branch |
|
|
180
|
+
| `comment-on-pr` | Set to `false` to suppress the PR report |
|
|
181
|
+
|
|
182
|
+
## Low-level actions
|
|
183
|
+
|
|
184
|
+
For custom orchestration:
|
|
185
|
+
|
|
186
|
+
- `actions/capture`
|
|
187
|
+
- `actions/compare`
|
|
188
|
+
- `actions/scope`
|
|
189
|
+
- `actions/enforce`
|
|
190
|
+
- `actions/comment`
|
|
191
|
+
- `actions/resolve-baseline`
|
|
192
|
+
- `actions/stage`
|
|
193
|
+
|
|
194
|
+
## Local development
|
|
195
|
+
|
|
196
|
+
The `snapdrift` CLI lets you run captures and diffs locally against a running app. Use it to validate UI changes on your machine before pushing — the same capture and comparison engine is used in both contexts.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Establish a baseline before making UI changes
|
|
200
|
+
snapdrift capture
|
|
201
|
+
|
|
202
|
+
# After making changes, compare and open the HTML report
|
|
203
|
+
snapdrift diff --open
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The CLI reads the same `.github/snapdrift.json` config used by the Actions workflow. Local outputs land in `.snapdrift/` by default and can be overridden with `--baseline-dir`, `--current-dir`, and `--diff-dir`.
|
|
207
|
+
|
|
208
|
+
See the [Local CLI guide](local-cli.md) for full command reference, flags, directory layout, and examples.
|
|
209
|
+
|
|
210
|
+
## Troubleshooting
|
|
211
|
+
|
|
212
|
+
**"No non-expired SnapDrift baseline artifact was found"**
|
|
213
|
+
The baseline workflow has not completed successfully on `main`, or the artifact expired.
|
|
214
|
+
|
|
215
|
+
**403 when posting the PR report**
|
|
216
|
+
Grant `issues: write` and `pull-requests: write` to the job.
|
|
217
|
+
|
|
218
|
+
**Screenshots have different dimensions**
|
|
219
|
+
SnapDrift reports this as a dimension shift and skips pixel comparison for that route. Refresh the baseline after the change lands.
|
|
220
|
+
|
|
221
|
+
**A route appears in `errors[]`**
|
|
222
|
+
Capture failed before comparison. Confirm the app is fully ready and reachable before SnapDrift runs.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Local CLI
|
|
2
|
+
|
|
3
|
+
The `snapdrift` CLI lets you run visual captures and diffs locally against a running app — no GitHub Actions required. Use it during development to validate UI changes before pushing.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node >= 22
|
|
8
|
+
- A running app reachable at the `baseUrl` in your config
|
|
9
|
+
- A `.github/snapdrift.json` config file (or pass `--config` to point to another path)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
The CLI ships with the `snapdrift` package. Install it globally or use `npx`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# global
|
|
17
|
+
npm install -g snapdrift
|
|
18
|
+
|
|
19
|
+
# or without installing
|
|
20
|
+
npx snapdrift <command>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
### `snapdrift capture`
|
|
26
|
+
|
|
27
|
+
Captures full-page screenshots of all configured routes and saves them as a local baseline.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
snapdrift capture [options]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Run this once to establish the baseline before making UI changes.
|
|
34
|
+
|
|
35
|
+
**Options**
|
|
36
|
+
|
|
37
|
+
| Flag | Default | Description |
|
|
38
|
+
|:-----|:--------|:------------|
|
|
39
|
+
| `--config <path>` | `.github/snapdrift.json` | Path to the config file |
|
|
40
|
+
| `--routes <ids>` | all routes | Comma-separated route IDs to capture |
|
|
41
|
+
| `--baseline-dir <path>` | `.snapdrift/baseline` | Directory to write baseline screenshots and metadata |
|
|
42
|
+
|
|
43
|
+
**Example**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
snapdrift capture
|
|
47
|
+
snapdrift capture --routes home-desktop,home-mobile
|
|
48
|
+
snapdrift capture --baseline-dir snapshots/baseline
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### `snapdrift diff`
|
|
54
|
+
|
|
55
|
+
Captures current screenshots, compares them against the local baseline, and writes a JSON summary, a markdown report, and a self-contained HTML report.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
snapdrift diff [options]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Options**
|
|
62
|
+
|
|
63
|
+
| Flag | Default | Description |
|
|
64
|
+
|:-----|:--------|:------------|
|
|
65
|
+
| `--config <path>` | `.github/snapdrift.json` | Path to the config file |
|
|
66
|
+
| `--routes <ids>` | all routes | Comma-separated route IDs to compare |
|
|
67
|
+
| `--baseline-dir <path>` | `.snapdrift/baseline` | Directory containing the baseline |
|
|
68
|
+
| `--current-dir <path>` | `.snapdrift/current` | Directory to write current screenshots |
|
|
69
|
+
| `--diff-dir <path>` | `.snapdrift/diff` | Directory to write the diff report |
|
|
70
|
+
| `--open` | off | Open the HTML report automatically after the diff |
|
|
71
|
+
|
|
72
|
+
**Example**
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
snapdrift diff
|
|
76
|
+
snapdrift diff --open
|
|
77
|
+
snapdrift diff --routes home-desktop --open
|
|
78
|
+
snapdrift diff --baseline-dir snapshots/baseline --diff-dir snapshots/diff
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Typical local workflow
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 1. Start your app
|
|
87
|
+
npm start
|
|
88
|
+
|
|
89
|
+
# 2. Capture a baseline (once, before making changes)
|
|
90
|
+
snapdrift capture
|
|
91
|
+
|
|
92
|
+
# 3. Make your UI changes
|
|
93
|
+
|
|
94
|
+
# 4. Run a diff to see what changed
|
|
95
|
+
snapdrift diff --open
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `--open` flag opens the HTML report in your default browser when the diff is complete.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Local directory layout
|
|
103
|
+
|
|
104
|
+
After running `capture` and `diff` with default paths:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
.snapdrift/
|
|
108
|
+
baseline/
|
|
109
|
+
results.json # capture metadata (status, dimensions, timing)
|
|
110
|
+
manifest.json # screenshot manifest (ids, paths, dimensions)
|
|
111
|
+
screenshots/
|
|
112
|
+
home-desktop.png
|
|
113
|
+
home-mobile.png
|
|
114
|
+
current/
|
|
115
|
+
results.json
|
|
116
|
+
manifest.json
|
|
117
|
+
screenshots/
|
|
118
|
+
home-desktop.png
|
|
119
|
+
home-mobile.png
|
|
120
|
+
diff/
|
|
121
|
+
summary.json # structured drift summary
|
|
122
|
+
summary.md # human-readable markdown report
|
|
123
|
+
report.html # self-contained HTML report with side-by-side images
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Add `.snapdrift/` to your `.gitignore` to keep local run output out of version control.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Exit codes
|
|
131
|
+
|
|
132
|
+
| Code | Meaning |
|
|
133
|
+
|:-----|:--------|
|
|
134
|
+
| `0` | Clean — no drift above threshold, or `diff.mode` is `report-only` |
|
|
135
|
+
| `1` | Drift enforced — `diff.mode` caused the run to fail (see [Drift modes](../README.md#drift-modes)) |
|
|
136
|
+
|
|
137
|
+
Enforcement follows the same `diff.mode` rules as the GitHub Actions workflow. Set `"mode": "report-only"` during local development to always get a report without a failing exit code.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Console output
|
|
142
|
+
|
|
143
|
+
`snapdrift diff` prints a summary to stdout:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Capturing current screenshots to .snapdrift/current ...
|
|
147
|
+
Comparing against baseline ...
|
|
148
|
+
|
|
149
|
+
✅ SnapDrift — Clean
|
|
150
|
+
Routes: 2
|
|
151
|
+
Matched: 2
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
When drift is detected:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
🟡 SnapDrift — Drift detected
|
|
158
|
+
Routes: 3
|
|
159
|
+
Matched: 2
|
|
160
|
+
Changed: 1
|
|
161
|
+
|
|
162
|
+
Changed routes:
|
|
163
|
+
• home-desktop (2.47% diff)
|
|
164
|
+
|
|
165
|
+
Report: .snapdrift/diff/report.html
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Tips
|
|
171
|
+
|
|
172
|
+
- **Partial runs**: use `--routes` to capture or compare only the routes you are actively changing.
|
|
173
|
+
- **Multiple baselines**: use `--baseline-dir` to maintain separate baselines per branch or feature.
|
|
174
|
+
- **CI parity**: the CLI uses the same capture and comparison engine as the GitHub Actions workflow, so results are comparable.
|