snapdrift 0.7.0 → 0.8.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/CHANGELOG.md +18 -0
- package/README.md +7 -2
- package/action.yml +175 -0
- package/actions/baseline/action.yml +4 -0
- package/actions/capture/action.yml +4 -0
- package/actions/comment/action.yml +4 -0
- package/actions/compare/action.yml +4 -0
- package/actions/enforce/action.yml +4 -0
- package/actions/pr-diff/action.yml +4 -0
- package/actions/resolve-baseline/action.yml +4 -0
- package/actions/scope/action.yml +4 -0
- package/actions/stage/action.yml +4 -0
- package/docs/integration-guide.md +25 -4
- package/docs/workflow-templates/refresh-baseline-on-merge.yml +2 -1
- package/lib/init-from-action.mjs +9 -7
- package/lib/migrate-baselines.mjs +4 -2
- package/lib/snap-provider.mjs +6 -5
- package/package.json +3 -2
- package/types/visual-diff-types.d.ts +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1 - 2026-08-06
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **The root action can actually be published to the Marketplace** — its description was 130 characters, and GitHub rejects the listing at 125 or more, so ticking "Publish this Action to the GitHub Marketplace" on the `v0.8.0` release failed validation. Shortened to 115 characters, leading with "visual regression" so the listing matches what people search for. `npm run validate:actions` and `tests/marketplace-metadata.test.js` now enforce the limit against the root action; the sub-action descriptions are unaffected, since the rule only applies to the published entry point.
|
|
8
|
+
|
|
9
|
+
## 0.8.0 - 2026-08-06
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **Root `action.yml` for the GitHub Marketplace** — SnapDrift's nine composite actions all live under `actions/*`, and Marketplace lists exactly one action per repository whose metadata file must sit at the repository root, so none of them were publishable. `ranacseruet/snapdrift@vX` is now a dispatcher action that runs the baseline pipeline with `mode: baseline` and the pull request pipeline with `mode: pr-diff`, forwarding every input and output of the corresponding wrapper. Existing `ranacseruet/snapdrift/actions/*@vX` references are unaffected and stay supported. The dispatcher pins absolute inner refs rather than `uses: ./actions/...`, because a relative `uses:` inside a composite action resolves against the caller's workspace; `tests/marketplace-metadata.test.js` fails the build if those pins drift from the package version. Released versions pin the inner ref to a commit SHA so that a consumer pinning the dispatcher to a SHA gets an immutable chain rather than a mutable tag one level down.
|
|
14
|
+
- **`branding` on every action** — required for the Marketplace listing, and applied to all nine sub-actions so the metadata stays consistent if any of them is ever split into its own repository. `npm run validate:actions` now rejects a missing icon or a color outside GitHub's allowed set, and covers the root action.
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- Updated the README, Integration Guide, and baseline workflow template action examples to use the current release.
|
|
19
|
+
- Documented the root action as the primary entry point across the README quickstart, the Integration Guide, and the baseline workflow template, with a new "Choosing an entry point" section mapping each `mode` to its equivalent wrapper action. The documentation version contract test now covers root-action references and prose references, not just `uses:` lines for the two wrappers.
|
|
20
|
+
|
|
3
21
|
## 0.7.0 - 2026-07-26
|
|
4
22
|
|
|
5
23
|
### Features
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|

|
|
4
4
|
|
|
5
5
|

|
|
6
|
+
[](https://github.com/marketplace/actions/snapdrift)
|
|
6
7
|

|
|
7
8
|

|
|
8
9
|
|
|
@@ -47,8 +48,9 @@ You keep ownership of checkout, build, startup, readiness, and teardown. SnapDri
|
|
|
47
48
|
|
|
48
49
|
```yaml
|
|
49
50
|
- name: SnapDrift Baseline
|
|
50
|
-
uses: ranacseruet/snapdrift
|
|
51
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
51
52
|
with:
|
|
53
|
+
mode: baseline
|
|
52
54
|
repo-config-path: .github/snapdrift.json
|
|
53
55
|
```
|
|
54
56
|
|
|
@@ -56,14 +58,17 @@ You keep ownership of checkout, build, startup, readiness, and teardown. SnapDri
|
|
|
56
58
|
|
|
57
59
|
```yaml
|
|
58
60
|
- name: SnapDrift Report
|
|
59
|
-
uses: ranacseruet/snapdrift
|
|
61
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
60
62
|
with:
|
|
63
|
+
mode: pr-diff
|
|
61
64
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
62
65
|
repo-config-path: .github/snapdrift.json
|
|
63
66
|
```
|
|
64
67
|
|
|
65
68
|
That is the full integration. See the [Integration Guide](docs/integration-guide.md) for workflow examples, permissions, compatibility notes, advanced overrides, and the hosted Snap backend (`provider: "snap"`).
|
|
66
69
|
|
|
70
|
+
The root action dispatches on `mode`. The same pipelines are also published as standalone actions — `ranacseruet/snapdrift/actions/baseline` and `ranacseruet/snapdrift/actions/pr-diff` — along with the lower-level `capture`, `compare`, `scope`, `resolve-baseline`, `stage`, `comment`, and `enforce` actions for custom orchestration.
|
|
71
|
+
|
|
67
72
|
## Local CLI
|
|
68
73
|
|
|
69
74
|
SnapDrift ships a `snapdrift` CLI for running captures, diffs, migrations, and config initialization locally against a running app — no GitHub Actions required. Use it during development to validate UI changes before pushing.
|
package/action.yml
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
name: SnapDrift
|
|
2
|
+
description: Visual regression for Playwright apps — capture routes, diff against a baseline, and report drift on pull requests.
|
|
3
|
+
author: CodeSamplez
|
|
4
|
+
|
|
5
|
+
branding:
|
|
6
|
+
icon: eye
|
|
7
|
+
color: purple
|
|
8
|
+
|
|
9
|
+
# GitHub Marketplace lists exactly one action per repository, and its metadata file must live at
|
|
10
|
+
# the repository root. This dispatcher is that entry point: it forwards to the wrapper actions
|
|
11
|
+
# under `actions/`, which remain directly usable (and unchanged) for custom orchestration.
|
|
12
|
+
#
|
|
13
|
+
# The inner `uses:` refs are pinned to the current release rather than a relative path because
|
|
14
|
+
# `uses: ./path` inside a composite action resolves against the *caller's* workspace, not the
|
|
15
|
+
# action's own directory. `tests/marketplace-metadata.test.js` fails the build if those pins drift
|
|
16
|
+
# from the root `package.json` version.
|
|
17
|
+
#
|
|
18
|
+
# The pins are commit SHAs, so that a consumer pinning this action to a SHA gets an immutable chain
|
|
19
|
+
# rather than a mutable tag one level down. The SHA is the commit holding the wrapper content this
|
|
20
|
+
# release ships — not the release tag itself, which a release commit cannot reference. It therefore
|
|
21
|
+
# only moves when the wrappers change. The trailing comment records the release the pin was last
|
|
22
|
+
# verified for, and the test requires it to match the package version, so every release has to look
|
|
23
|
+
# at it. The test accepts a plain `@vX.Y.Z` tag too, for unreleased refs.
|
|
24
|
+
|
|
25
|
+
inputs:
|
|
26
|
+
mode:
|
|
27
|
+
description: Which SnapDrift pipeline to run — `pr-diff` for pull request drift detection, or `baseline` to publish a baseline.
|
|
28
|
+
required: false
|
|
29
|
+
default: pr-diff
|
|
30
|
+
repo-config-path:
|
|
31
|
+
description: Path to the SnapDrift config file.
|
|
32
|
+
required: false
|
|
33
|
+
default: .github/snapdrift.json
|
|
34
|
+
route-ids:
|
|
35
|
+
description: Optional comma-separated route ids. In `pr-diff` mode, providing these skips changed-file scope detection.
|
|
36
|
+
required: false
|
|
37
|
+
default: ''
|
|
38
|
+
artifact-retention-days:
|
|
39
|
+
description: Retention period for the uploaded artifact.
|
|
40
|
+
required: false
|
|
41
|
+
default: '30'
|
|
42
|
+
github-token:
|
|
43
|
+
description: Token used to inspect pull request files, fetch baselines, and publish SnapDrift PR reports. Used in `pr-diff` mode only.
|
|
44
|
+
required: false
|
|
45
|
+
default: ${{ github.token }}
|
|
46
|
+
|
|
47
|
+
# `baseline` mode only.
|
|
48
|
+
upload-artifact:
|
|
49
|
+
description: Whether to upload the staged baseline artifact. Used in `baseline` mode only.
|
|
50
|
+
required: false
|
|
51
|
+
default: 'true'
|
|
52
|
+
|
|
53
|
+
# `pr-diff` mode only.
|
|
54
|
+
pr-number:
|
|
55
|
+
description: Optional pull request number override.
|
|
56
|
+
required: false
|
|
57
|
+
default: ''
|
|
58
|
+
force-run:
|
|
59
|
+
description: Whether to force a full run when route ids are not explicitly provided.
|
|
60
|
+
required: false
|
|
61
|
+
default: 'false'
|
|
62
|
+
force-run-reason:
|
|
63
|
+
description: Reason to report when force-run is enabled.
|
|
64
|
+
required: false
|
|
65
|
+
default: forced
|
|
66
|
+
baseline-repository:
|
|
67
|
+
description: Repository in owner/name format that publishes the baseline.
|
|
68
|
+
required: false
|
|
69
|
+
default: ''
|
|
70
|
+
baseline-workflow-id:
|
|
71
|
+
description: Workflow id or filename to search for the baseline.
|
|
72
|
+
required: false
|
|
73
|
+
default: ci.yml
|
|
74
|
+
baseline-branch:
|
|
75
|
+
description: Branch that publishes the baseline.
|
|
76
|
+
required: false
|
|
77
|
+
default: main
|
|
78
|
+
comment-on-pr:
|
|
79
|
+
description: Whether to publish the SnapDrift PR report.
|
|
80
|
+
required: false
|
|
81
|
+
default: 'true'
|
|
82
|
+
max-changed-rows:
|
|
83
|
+
description: Maximum number of changed-route rows shown in the PR comment before truncation.
|
|
84
|
+
required: false
|
|
85
|
+
default: '20'
|
|
86
|
+
max-error-rows:
|
|
87
|
+
description: Maximum number of error rows shown in the PR comment before truncation.
|
|
88
|
+
required: false
|
|
89
|
+
default: '10'
|
|
90
|
+
|
|
91
|
+
outputs:
|
|
92
|
+
# Shared across both modes.
|
|
93
|
+
artifact-name:
|
|
94
|
+
description: Name of the uploaded artifact.
|
|
95
|
+
value: ${{ steps.baseline.outputs.artifact-name || steps.pr-diff.outputs.artifact-name }}
|
|
96
|
+
bundle-dir:
|
|
97
|
+
description: Staged artifact bundle directory.
|
|
98
|
+
value: ${{ steps.baseline.outputs.bundle-dir || steps.pr-diff.outputs.bundle-dir }}
|
|
99
|
+
selected-route-ids:
|
|
100
|
+
description: Comma-separated route ids selected for the run.
|
|
101
|
+
value: ${{ steps.baseline.outputs.selected-route-ids || steps.pr-diff.outputs.selected-route-ids }}
|
|
102
|
+
|
|
103
|
+
# `baseline` mode only.
|
|
104
|
+
results-file:
|
|
105
|
+
description: Absolute path to the generated baseline results file.
|
|
106
|
+
value: ${{ steps.baseline.outputs.results-file }}
|
|
107
|
+
manifest-file:
|
|
108
|
+
description: Absolute path to the generated baseline manifest file.
|
|
109
|
+
value: ${{ steps.baseline.outputs.manifest-file }}
|
|
110
|
+
screenshots-root:
|
|
111
|
+
description: Absolute path to the generated screenshots root.
|
|
112
|
+
value: ${{ steps.baseline.outputs.screenshots-root }}
|
|
113
|
+
|
|
114
|
+
# `pr-diff` mode only.
|
|
115
|
+
should-run:
|
|
116
|
+
description: Whether the SnapDrift flow ran or intentionally skipped due to scope.
|
|
117
|
+
value: ${{ steps.pr-diff.outputs.should-run }}
|
|
118
|
+
scope-reason:
|
|
119
|
+
description: Reason for the final scope decision.
|
|
120
|
+
value: ${{ steps.pr-diff.outputs.scope-reason }}
|
|
121
|
+
baseline-found:
|
|
122
|
+
description: Whether a usable baseline was found.
|
|
123
|
+
value: ${{ steps.pr-diff.outputs.baseline-found }}
|
|
124
|
+
status:
|
|
125
|
+
description: Final SnapDrift status.
|
|
126
|
+
value: ${{ steps.pr-diff.outputs.status }}
|
|
127
|
+
summary-path:
|
|
128
|
+
description: Path to the final summary JSON file.
|
|
129
|
+
value: ${{ steps.pr-diff.outputs.summary-path }}
|
|
130
|
+
markdown-path:
|
|
131
|
+
description: Path to the final summary markdown file.
|
|
132
|
+
value: ${{ steps.pr-diff.outputs.markdown-path }}
|
|
133
|
+
|
|
134
|
+
runs:
|
|
135
|
+
using: composite
|
|
136
|
+
steps:
|
|
137
|
+
- name: Validate mode
|
|
138
|
+
shell: bash
|
|
139
|
+
env:
|
|
140
|
+
MODE: ${{ inputs.mode }}
|
|
141
|
+
run: |
|
|
142
|
+
set -euo pipefail
|
|
143
|
+
if [ "$MODE" != "pr-diff" ] && [ "$MODE" != "baseline" ]; then
|
|
144
|
+
echo "::error::Unsupported SnapDrift mode '$MODE'. Expected 'pr-diff' or 'baseline'."
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
- name: SnapDrift baseline
|
|
149
|
+
id: baseline
|
|
150
|
+
if: inputs.mode == 'baseline'
|
|
151
|
+
uses: ranacseruet/snapdrift/actions/baseline@539953ab07065be805be393d69bd3c9b6d53cf6e # v0.8.1
|
|
152
|
+
with:
|
|
153
|
+
repo-config-path: ${{ inputs.repo-config-path }}
|
|
154
|
+
route-ids: ${{ inputs.route-ids }}
|
|
155
|
+
artifact-retention-days: ${{ inputs.artifact-retention-days }}
|
|
156
|
+
upload-artifact: ${{ inputs.upload-artifact }}
|
|
157
|
+
|
|
158
|
+
- name: SnapDrift pull request diff
|
|
159
|
+
id: pr-diff
|
|
160
|
+
if: inputs.mode == 'pr-diff'
|
|
161
|
+
uses: ranacseruet/snapdrift/actions/pr-diff@539953ab07065be805be393d69bd3c9b6d53cf6e # v0.8.1
|
|
162
|
+
with:
|
|
163
|
+
github-token: ${{ inputs.github-token }}
|
|
164
|
+
repo-config-path: ${{ inputs.repo-config-path }}
|
|
165
|
+
pr-number: ${{ inputs.pr-number }}
|
|
166
|
+
route-ids: ${{ inputs.route-ids }}
|
|
167
|
+
force-run: ${{ inputs.force-run }}
|
|
168
|
+
force-run-reason: ${{ inputs.force-run-reason }}
|
|
169
|
+
baseline-repository: ${{ inputs.baseline-repository }}
|
|
170
|
+
baseline-workflow-id: ${{ inputs.baseline-workflow-id }}
|
|
171
|
+
baseline-branch: ${{ inputs.baseline-branch }}
|
|
172
|
+
artifact-retention-days: ${{ inputs.artifact-retention-days }}
|
|
173
|
+
comment-on-pr: ${{ inputs.comment-on-pr }}
|
|
174
|
+
max-changed-rows: ${{ inputs.max-changed-rows }}
|
|
175
|
+
max-error-rows: ${{ inputs.max-error-rows }}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
name: SnapDrift Baseline
|
|
2
2
|
description: Capture configured routes, stage a SnapDrift baseline, and upload it with the configured artifact name.
|
|
3
3
|
|
|
4
|
+
branding:
|
|
5
|
+
icon: camera
|
|
6
|
+
color: purple
|
|
7
|
+
|
|
4
8
|
inputs:
|
|
5
9
|
repo-config-path:
|
|
6
10
|
description: Path to the SnapDrift config file.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
name: SnapDrift PR Diff
|
|
2
2
|
description: Run the end-to-end SnapDrift pull request pipeline, including scope resolution, skipped-report handling, artifact upload, PR reporting, and enforcement.
|
|
3
3
|
|
|
4
|
+
branding:
|
|
5
|
+
icon: eye
|
|
6
|
+
color: purple
|
|
7
|
+
|
|
4
8
|
inputs:
|
|
5
9
|
github-token:
|
|
6
10
|
description: Token used to inspect pull request files, fetch baselines, and publish SnapDrift PR reports.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
name: SnapDrift Resolve Baseline
|
|
2
2
|
description: Resolve and download the latest successful SnapDrift baseline from main.
|
|
3
3
|
|
|
4
|
+
branding:
|
|
5
|
+
icon: download-cloud
|
|
6
|
+
color: purple
|
|
7
|
+
|
|
4
8
|
inputs:
|
|
5
9
|
github-token:
|
|
6
10
|
description: Token used to query workflow runs and download artifacts.
|
package/actions/scope/action.yml
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
name: SnapDrift Scope
|
|
2
2
|
description: Determine whether SnapDrift should run and which configured routes should be selected for the current change set.
|
|
3
3
|
|
|
4
|
+
branding:
|
|
5
|
+
icon: filter
|
|
6
|
+
color: purple
|
|
7
|
+
|
|
4
8
|
inputs:
|
|
5
9
|
github-token:
|
|
6
10
|
description: Token used to query pull request changed files.
|
package/actions/stage/action.yml
CHANGED
|
@@ -46,8 +46,9 @@ SnapDrift owns route selection, capture, comparison, skipped-report generation,
|
|
|
46
46
|
|
|
47
47
|
```yaml
|
|
48
48
|
- name: SnapDrift Baseline
|
|
49
|
-
uses: ranacseruet/snapdrift
|
|
49
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
50
50
|
with:
|
|
51
|
+
mode: baseline
|
|
51
52
|
repo-config-path: .github/snapdrift.json
|
|
52
53
|
artifact-retention-days: '30'
|
|
53
54
|
```
|
|
@@ -70,12 +71,30 @@ Then add SnapDrift after the app is running:
|
|
|
70
71
|
|
|
71
72
|
```yaml
|
|
72
73
|
- name: SnapDrift Report
|
|
73
|
-
uses: ranacseruet/snapdrift
|
|
74
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
74
75
|
with:
|
|
76
|
+
mode: pr-diff
|
|
75
77
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
76
78
|
repo-config-path: .github/snapdrift.json
|
|
77
79
|
```
|
|
78
80
|
|
|
81
|
+
## Choosing an entry point
|
|
82
|
+
|
|
83
|
+
`ranacseruet/snapdrift` is the GitHub Marketplace entry point. It is a thin dispatcher: `mode: baseline` runs the baseline pipeline and `mode: pr-diff` runs the pull request pipeline, forwarding every input and output of the underlying wrapper action.
|
|
84
|
+
|
|
85
|
+
The wrappers remain published in their own right, and are equivalent to the dispatcher:
|
|
86
|
+
|
|
87
|
+
| Entry point | Equivalent to |
|
|
88
|
+
|-------------|---------------|
|
|
89
|
+
| `ranacseruet/snapdrift@v0.8.1` with `mode: baseline` | `ranacseruet/snapdrift/actions/baseline@v0.8.1` |
|
|
90
|
+
| `ranacseruet/snapdrift@v0.8.1` with `mode: pr-diff` | `ranacseruet/snapdrift/actions/pr-diff@v0.8.1` |
|
|
91
|
+
|
|
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
|
+
|
|
94
|
+
### Pinning
|
|
95
|
+
|
|
96
|
+
The dispatcher loads its wrapper from a pinned ref of this same repository. Released versions pin that ref to a commit SHA, so resolving `ranacseruet/snapdrift` to a SHA freezes the whole chain. If you are consuming an unreleased ref (`@main`, or a branch), the inner ref is a tag instead, and a SHA pin on the dispatcher does not freeze what it delegates to — pin the wrapper actions directly if that matters to you.
|
|
97
|
+
|
|
79
98
|
## Example workflow (Node app)
|
|
80
99
|
|
|
81
100
|
```yaml
|
|
@@ -111,8 +130,9 @@ jobs:
|
|
|
111
130
|
done
|
|
112
131
|
|
|
113
132
|
- name: SnapDrift Report
|
|
114
|
-
uses: ranacseruet/snapdrift
|
|
133
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
115
134
|
with:
|
|
135
|
+
mode: pr-diff
|
|
116
136
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
117
137
|
repo-config-path: .github/snapdrift.json
|
|
118
138
|
```
|
|
@@ -150,8 +170,9 @@ jobs:
|
|
|
150
170
|
done
|
|
151
171
|
|
|
152
172
|
- name: SnapDrift Report
|
|
153
|
-
uses: ranacseruet/snapdrift
|
|
173
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
154
174
|
with:
|
|
175
|
+
mode: pr-diff
|
|
155
176
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
156
177
|
repo-config-path: .github/snapdrift.json
|
|
157
178
|
```
|
|
@@ -70,8 +70,9 @@ jobs:
|
|
|
70
70
|
# ────────────────────────────────────────────────────────────────────────
|
|
71
71
|
|
|
72
72
|
- name: Publish SnapDrift baseline
|
|
73
|
-
uses: ranacseruet/snapdrift
|
|
73
|
+
uses: ranacseruet/snapdrift@v0.8.1
|
|
74
74
|
with:
|
|
75
|
+
mode: baseline
|
|
75
76
|
repo-config-path: .github/snapdrift.json
|
|
76
77
|
artifact-retention-days: '30'
|
|
77
78
|
|
package/lib/init-from-action.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import * as yaml from 'js-yaml';
|
|
6
6
|
|
|
7
7
|
/** @typedef {import('../types/visual-diff-types').InitWarning} InitWarning */
|
|
8
|
+
/** @typedef {import('../types/visual-diff-types').JsonObject} JsonObject */
|
|
8
9
|
|
|
9
10
|
const DEFAULT_OUTPUT_DIR = '.github';
|
|
10
11
|
const DEFAULT_CONFIG_FILENAME = 'snapdrift.json';
|
|
@@ -13,8 +14,8 @@ const MIGRATION_NOTES_FILENAME = 'MIGRATION_NOTES.md';
|
|
|
13
14
|
/**
|
|
14
15
|
* Find the step that uses the Snap visual testing action.
|
|
15
16
|
*
|
|
16
|
-
* @param {
|
|
17
|
-
* @returns {{ step:
|
|
17
|
+
* @param {JsonObject} workflow - Parsed YAML workflow
|
|
18
|
+
* @returns {{ step: JsonObject, jobName: string, stepIndex: number } | null}
|
|
18
19
|
*/
|
|
19
20
|
function findSnapActionStep(workflow) {
|
|
20
21
|
const jobs = workflow.jobs || {};
|
|
@@ -34,7 +35,7 @@ function findSnapActionStep(workflow) {
|
|
|
34
35
|
/**
|
|
35
36
|
* Map Snap action enforcement mode to snapdrift diff.mode.
|
|
36
37
|
*
|
|
37
|
-
* @param {
|
|
38
|
+
* @param {JsonObject} inputs
|
|
38
39
|
* @returns {string}
|
|
39
40
|
*/
|
|
40
41
|
function mapDiffMode(inputs) {
|
|
@@ -52,7 +53,7 @@ function mapDiffMode(inputs) {
|
|
|
52
53
|
/**
|
|
53
54
|
* Extract Snap action inputs and translate to snapdrift config fields.
|
|
54
55
|
*
|
|
55
|
-
* @param {
|
|
56
|
+
* @param {JsonObject} step - The workflow step using the Snap action
|
|
56
57
|
* @returns {{ config: object, warnings: InitWarning[] }}
|
|
57
58
|
*/
|
|
58
59
|
function translateStepToConfig(step) {
|
|
@@ -87,7 +88,7 @@ function translateStepToConfig(step) {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// Snap provider config (if present in inputs)
|
|
90
|
-
/** @type {
|
|
91
|
+
/** @type {JsonObject | undefined} */
|
|
91
92
|
let snap;
|
|
92
93
|
const snapApiUrl = inputs['snap-api-url'] || inputs.snap_api_url;
|
|
93
94
|
const snapApiKeyEnv = inputs['snap-api-key-env'] || inputs.snap_api_key_env;
|
|
@@ -195,10 +196,11 @@ export async function runInitFromAction(actionYamlPath) {
|
|
|
195
196
|
throw new Error(`Cannot read workflow file: ${actionYamlPath}`);
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
const
|
|
199
|
-
if (!
|
|
199
|
+
const parsedWorkflow = yaml.load(workflowContent);
|
|
200
|
+
if (!parsedWorkflow || typeof parsedWorkflow !== 'object') {
|
|
200
201
|
throw new Error(`Invalid workflow YAML: ${actionYamlPath}`);
|
|
201
202
|
}
|
|
203
|
+
const workflow = /** @type {JsonObject} */ (parsedWorkflow);
|
|
202
204
|
|
|
203
205
|
// Find the Snap action step
|
|
204
206
|
const snapStep = findSnapActionStep(workflow);
|
|
@@ -6,6 +6,8 @@ import path from 'node:path';
|
|
|
6
6
|
|
|
7
7
|
import { createProvider } from './provider.mjs';
|
|
8
8
|
|
|
9
|
+
/** @typedef {import('../types/visual-diff-types').JsonObject} JsonObject */
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Resolve the current commit HEAD SHA.
|
|
11
13
|
* Tries GITHUB_SHA first, then git rev-parse HEAD.
|
|
@@ -28,7 +30,7 @@ function resolveHeadSha() {
|
|
|
28
30
|
* Read local baseline files from a directory.
|
|
29
31
|
*
|
|
30
32
|
* @param {string} baselineDir
|
|
31
|
-
* @returns {Promise<{ results:
|
|
33
|
+
* @returns {Promise<{ results: JsonObject, manifest: JsonObject, screenshots: Array<{ filename: string, data: string }>, headSha: string }>}
|
|
32
34
|
*/
|
|
33
35
|
export async function readLocalBaselines(baselineDir) {
|
|
34
36
|
const resultsPath = path.join(baselineDir, 'results.json');
|
|
@@ -130,7 +132,7 @@ export async function runMigrateToLocal(config, opts) {
|
|
|
130
132
|
const provider = createProvider('snap', config);
|
|
131
133
|
const baselineDir = opts.baselineDir;
|
|
132
134
|
|
|
133
|
-
/** @type {{ results:
|
|
135
|
+
/** @type {{ results: JsonObject, manifest: JsonObject, screenshots: Array<{ filename: string, data: Buffer }>, engine: { name: string, version: string } }} */
|
|
134
136
|
let exported;
|
|
135
137
|
try {
|
|
136
138
|
exported = await provider.exportBaselines();
|
package/lib/snap-provider.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { selectConfiguredRoutes, splitCommaList, determineDriftStatus, VIEWPORT_
|
|
|
23
23
|
/** @typedef {import('../types/visual-diff-types').ProviderBaselineData} BaselineData */
|
|
24
24
|
/** @typedef {import('../types/visual-diff-types').SnapConfig} SnapConfig */
|
|
25
25
|
/** @typedef {import('../types/visual-diff-types').VisualRegressionConfig} VisualRegressionConfig */
|
|
26
|
+
/** @typedef {import('../types/visual-diff-types').JsonObject} JsonObject */
|
|
26
27
|
|
|
27
28
|
const DEFAULT_API_URL = 'https://snap.i2dev.com';
|
|
28
29
|
const MAX_RETRIES = 3;
|
|
@@ -667,8 +668,8 @@ export class SnapProvider {
|
|
|
667
668
|
*
|
|
668
669
|
* @param {{ tag?: string }} [_options]
|
|
669
670
|
* @returns {Promise<{
|
|
670
|
-
* results:
|
|
671
|
-
* manifest:
|
|
671
|
+
* results: JsonObject,
|
|
672
|
+
* manifest: JsonObject,
|
|
672
673
|
* screenshots: Array<{ filename: string, data: Buffer }>,
|
|
673
674
|
* engine: { name: string, version: string }
|
|
674
675
|
* }>}
|
|
@@ -751,7 +752,7 @@ export class SnapProvider {
|
|
|
751
752
|
|
|
752
753
|
/** @type {Array<{ filename: string, data: Buffer }>} */
|
|
753
754
|
const screenshots = [];
|
|
754
|
-
/** @type {
|
|
755
|
+
/** @type {JsonObject[]} */
|
|
755
756
|
const manifestEntries = [];
|
|
756
757
|
|
|
757
758
|
for (const route of routes) {
|
|
@@ -1209,7 +1210,7 @@ export class SnapProvider {
|
|
|
1209
1210
|
/**
|
|
1210
1211
|
* Poll a run until it reaches a terminal state.
|
|
1211
1212
|
* @param {string} runId
|
|
1212
|
-
* @returns {Promise<
|
|
1213
|
+
* @returns {Promise<JsonObject>}
|
|
1213
1214
|
*/
|
|
1214
1215
|
async #pollRun(runId) {
|
|
1215
1216
|
const startTime = Date.now();
|
|
@@ -1229,7 +1230,7 @@ export class SnapProvider {
|
|
|
1229
1230
|
|
|
1230
1231
|
/**
|
|
1231
1232
|
* Map a Snap run result to a VisualDiffSummary.
|
|
1232
|
-
* @param {
|
|
1233
|
+
* @param {JsonObject} run
|
|
1233
1234
|
* @param {VisualRegressionConfig} config
|
|
1234
1235
|
* @returns {import('../types/visual-diff-types').VisualDiffSummary}
|
|
1235
1236
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snapdrift",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "SnapDrift captures, compares, and reports UI drift for Playwright-based app checks.",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"eslint": "^10.2.0",
|
|
47
47
|
"globals": "^17.4.0",
|
|
48
48
|
"jest": "^30.3.0",
|
|
49
|
-
"typescript": "^
|
|
49
|
+
"typescript": "^7.0.2"
|
|
50
50
|
},
|
|
51
51
|
"jest": {
|
|
52
52
|
"testEnvironment": "node",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"./lib/init-from-action.mjs": "./lib/init-from-action.mjs"
|
|
83
83
|
},
|
|
84
84
|
"files": [
|
|
85
|
+
"action.yml",
|
|
85
86
|
"actions/",
|
|
86
87
|
"bin/",
|
|
87
88
|
"lib/",
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** Object-shaped data decoded from an external JSON or YAML boundary. */
|
|
2
|
+
export type JsonObject = Record<string, any>;
|
|
3
|
+
|
|
1
4
|
export type VisualViewportPreset = 'desktop' | 'mobile';
|
|
2
5
|
export interface VisualCustomViewport { width: number; height: number; }
|
|
3
6
|
export type VisualViewport = VisualViewportPreset | VisualCustomViewport;
|
|
@@ -166,8 +169,8 @@ export interface MigrateToSnapResult {
|
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
export interface ExportedBaseline {
|
|
169
|
-
results:
|
|
170
|
-
manifest:
|
|
172
|
+
results: JsonObject;
|
|
173
|
+
manifest: JsonObject;
|
|
171
174
|
screenshots: Array<{ filename: string; data: Buffer }>;
|
|
172
175
|
engine: { name: string; version: string };
|
|
173
176
|
}
|