capybara-storyboard 0.1.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +24 -4
- data/docs/visual-regression.md +106 -0
- data/lib/capybara/storyboard/version.rb +1 -1
- data/skills/visual-regression-test/README.md +60 -0
- data/skills/visual-regression-test/SKILL.md +304 -0
- data/skills/visual-regression-test/scripts/reg-diff.sh +57 -0
- data/skills/visual-regression-test/scripts/resolve-baseline.sh +96 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c4545f0de7b20b1fbfcf71a08ed644553d5445001370ff3d59a4d8c9fc9c88f
|
|
4
|
+
data.tar.gz: a3aa8204023cab2f89f1799f0fbf7aee7fe6965bbbe00d1944e75c27010dc158
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4aed98cd98f7137740c85d5b9d994adbec0ecd2f9c06c972c7caacc5b4087e7450710855e267d507651ceb1f5d4f4667bfb1bdf8d0a9d0f037c5d39f27bcadb4
|
|
7
|
+
data.tar.gz: f801d661ab48e2d980fc39aaa7f8d9614a79a6c6b3795c7335f33263658a872d97894c8e7ad76fabf72bd51767d2dfbbc17ad45c056bd86661c4c425e8d1d8b6
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -108,7 +108,9 @@ which test files are captured.
|
|
|
108
108
|
| Selective capture | `1` | set via `SCREENSHOT_TESTS_FILE` / `SCREENSHOT_TESTS` | Only the listed test files are captured. If the resulting set is empty, zero screenshots are taken (an empty selection does NOT fall back to capturing everything). |
|
|
109
109
|
|
|
110
110
|
In short: `SCREENSHOTS` arms the mechanism as a whole, and the target list — when present —
|
|
111
|
-
filters down which tests are captured within that armed mechanism.
|
|
111
|
+
filters down which tests are captured within that armed mechanism. The target list affects
|
|
112
|
+
capture only, never test execution: RSpec still runs whatever you tell it to run (see
|
|
113
|
+
[Selecting which tests to capture](#selecting-which-tests-to-capture)).
|
|
112
114
|
|
|
113
115
|
## Selecting which tests to capture
|
|
114
116
|
|
|
@@ -126,14 +128,25 @@ If `SCREENSHOT_TESTS_FILE` points to a file that does not exist, `Capybara::Stor
|
|
|
126
128
|
is raised. (This existence check only runs when `SCREENSHOTS` is enabled; when the mechanism
|
|
127
129
|
is disabled, the target list is never read.)
|
|
128
130
|
|
|
131
|
+
> [!IMPORTANT]
|
|
132
|
+
> The target list narrows **which tests are captured**, not **which tests are run**. RSpec
|
|
133
|
+
> still executes every test you pass to it; the listed files are merely the ones that get
|
|
134
|
+
> screenshots. To also avoid running the other tests — the usual goal in CI — pass the target
|
|
135
|
+
> files to `rspec` as arguments instead of (or in addition to) using the target list. See the
|
|
136
|
+
> examples below.
|
|
137
|
+
|
|
129
138
|
Examples:
|
|
130
139
|
|
|
131
140
|
```bash
|
|
132
|
-
# Inline list of test files
|
|
141
|
+
# Inline list of test files (all system specs still RUN; only these two are CAPTURED)
|
|
133
142
|
SCREENSHOTS=1 SCREENSHOT_TESTS=spec/system/login_spec.rb,spec/system/signup_spec.rb bundle exec rspec
|
|
134
143
|
|
|
135
144
|
# List generated into a file (e.g. by CI, listing only changed specs)
|
|
136
145
|
SCREENSHOTS=1 SCREENSHOT_TESTS_FILE=tmp/screenshot_targets.txt bundle exec rspec
|
|
146
|
+
|
|
147
|
+
# Narrow both execution AND capture: pass the files to rspec as arguments.
|
|
148
|
+
# With SCREENSHOTS=1, every test that actually runs is captured, so no target list is needed.
|
|
149
|
+
SCREENSHOTS=1 bundle exec rspec $(cat tmp/screenshot_targets.txt)
|
|
137
150
|
```
|
|
138
151
|
|
|
139
152
|
## Output layout
|
|
@@ -232,16 +245,23 @@ is still taken. The wait is tunable via:
|
|
|
232
245
|
- Test files are expected to be laid out flat as `spec/system/*_spec.rb`. Nested
|
|
233
246
|
subdirectories may work but are not comprehensively verified in this initial version.
|
|
234
247
|
- The target-list selection matches on test file paths. A PR that changes only views
|
|
235
|
-
(leaving the system spec file itself unchanged) cannot be picked up by the
|
|
236
|
-
|
|
248
|
+
(leaving the system spec file itself unchanged) cannot be picked up by the basic
|
|
249
|
+
path-diff recipe in [docs/github-actions.md](docs/github-actions.md) — a known, deliberate
|
|
250
|
+
limitation of that recipe. The [docs/visual-regression.md](docs/visual-regression.md)
|
|
251
|
+
workflow lifts it via optional Claude-driven target expansion.
|
|
237
252
|
|
|
238
253
|
## Guides
|
|
239
254
|
|
|
240
255
|
- [docs/github-actions.md](docs/github-actions.md) — a recipe for capturing screenshots on a
|
|
241
256
|
diff basis in GitHub Actions.
|
|
257
|
+
- [docs/visual-regression.md](docs/visual-regression.md) — a reusable GitHub Actions workflow
|
|
258
|
+
for AI-assisted visual regression testing.
|
|
242
259
|
- [skills/capybara-storyboard](skills/capybara-storyboard) — an agent skill for visually
|
|
243
260
|
verifying captured screenshots (Claude Code, or any agent that can read PNG files from the
|
|
244
261
|
filesystem).
|
|
262
|
+
- [skills/visual-regression-test](skills/visual-regression-test) — an agent skill that
|
|
263
|
+
compares before/after screenshots with `reg-cli` to catch unintended visual regressions,
|
|
264
|
+
invoked via `/visual-regression-test [ref]` in Claude Code.
|
|
245
265
|
|
|
246
266
|
## Development
|
|
247
267
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Reusable GitHub Actions workflow: AI-assisted visual regression testing
|
|
2
|
+
|
|
3
|
+
This is a reusable workflow, not a copy-paste recipe: capture screenshots on the PR base and
|
|
4
|
+
head branches in parallel, pixel-diff them with [reg-cli](https://github.com/reg-viz/reg-cli),
|
|
5
|
+
have Claude classify each changed image as intended, a likely regression, or uncertain, and
|
|
6
|
+
report the result as a PR comment and job summary. It never blocks the PR — a human makes the
|
|
7
|
+
final call.
|
|
8
|
+
|
|
9
|
+
This complements the [`docs/github-actions.md`](github-actions.md) recipe rather than
|
|
10
|
+
replacing it. That recipe only captures screenshots and uploads them for manual review; it
|
|
11
|
+
doesn't detect what changed, and it can't pick up a PR that only touches views (not the spec
|
|
12
|
+
files themselves). This workflow adds automatic change detection on top, and — via optional
|
|
13
|
+
Claude-driven target expansion — can also capture specs affected by a view-only PR that never
|
|
14
|
+
touches a spec file.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Call it from a workflow in your application repository. Pin the `uses:` ref to one that
|
|
19
|
+
actually exists — the example below uses `@main`; a release tag such as `@v0.1.0`, or a
|
|
20
|
+
`@v1` major tag once one is published, work too (a ref that doesn't resolve fails with
|
|
21
|
+
"workflow was not found"):
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
name: Visual regression
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
pull_request:
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
visual-regression:
|
|
31
|
+
# A called workflow's token permissions are the intersection of what it
|
|
32
|
+
# requests and what the caller grants, so grant pull-requests: write here —
|
|
33
|
+
# otherwise the PR-comment step is denied (403) on repositories whose default
|
|
34
|
+
# workflow token is read-only.
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
pull-requests: write
|
|
38
|
+
uses: aki77/capybara-storyboard/.github/workflows/visual-regression.yml@main
|
|
39
|
+
with:
|
|
40
|
+
ruby_version: "3.4"
|
|
41
|
+
secrets:
|
|
42
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Inputs
|
|
46
|
+
|
|
47
|
+
| Input | Default | Description |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `base_ref` | `${{ github.event.pull_request.base.ref }}` | Base ref to compare against (the expected side). |
|
|
50
|
+
| `ruby_version` | `"3.4"` | Ruby version passed to `ruby/setup-ruby`. |
|
|
51
|
+
| `node_version` | `"20"` | Node.js version for reg-cli (requires Node 20+). |
|
|
52
|
+
| `reg_threshold` | `"0"` | `reg-cli --thresholdRate` (0..1). Fraction of changed pixels tolerated before an image counts as changed. |
|
|
53
|
+
| `enable_claude` | `true` | Have Claude classify the changed diff images (intended / regression / uncertain). |
|
|
54
|
+
| `enable_claude_target_expansion` | `true` | Have Claude expand the target spec list from view-facing changes (view/template/component/CSS). |
|
|
55
|
+
| `model` | `""` | Model to pass to `claude-code-action` (via `--model`). Empty uses the action default. |
|
|
56
|
+
| `spec_paths` | `"system,components"` | Comma-separated spec subdirectories under `spec/` to capture (e.g. `system,components`). |
|
|
57
|
+
|
|
58
|
+
### Secrets
|
|
59
|
+
|
|
60
|
+
| Secret | Required | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `anthropic_api_key` | No | Anthropic API key for `claude-code-action`. Only needed when Claude features are enabled. |
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
The workflow runs four jobs:
|
|
67
|
+
|
|
68
|
+
1. **targets** — diffs `base_ref` against head to find changed spec files under `spec_paths`,
|
|
69
|
+
optionally asks Claude to expand that list from view-facing changes elsewhere in the diff,
|
|
70
|
+
and uploads the final list as an artifact.
|
|
71
|
+
2. **shoot-base** / **shoot-head** — each checks out its own ref, runs only the target specs
|
|
72
|
+
with `SCREENSHOTS=1`, and uploads the resulting screenshots. Both depend only on `targets`,
|
|
73
|
+
so **they run in parallel**.
|
|
74
|
+
3. **compare** — downloads both screenshot sets, pixel-diffs them with reg-cli, has Claude
|
|
75
|
+
classify the changed images (when enabled), and posts a PR comment and job summary. This
|
|
76
|
+
check never blocks the PR.
|
|
77
|
+
|
|
78
|
+
## Reading the artifacts
|
|
79
|
+
|
|
80
|
+
The compare job uploads two artifacts:
|
|
81
|
+
|
|
82
|
+
- **`vr-flagged`** — only the diff images Claude classified as `regression` or `uncertain`,
|
|
83
|
+
uploaded uncompressed so the Actions UI can preview each PNG natively. Start here: it's
|
|
84
|
+
the small set of images that actually need a human look.
|
|
85
|
+
- **`vr-full`** — `report.html`, `reg.json`, and the full `diff` image set, as a normal zip.
|
|
86
|
+
Download and open `report.html` locally for the complete reg-cli report, or to inspect a
|
|
87
|
+
diff image that wasn't flagged. The base and head screenshots themselves are the separate
|
|
88
|
+
**`vr-shots-base`** / **`vr-shots-head`** artifacts.
|
|
89
|
+
|
|
90
|
+
## Notes
|
|
91
|
+
|
|
92
|
+
- Set `enable_claude: false` to disable Claude classification entirely (reg-cli diffing still
|
|
93
|
+
runs); `anthropic_api_key` is not required in that case.
|
|
94
|
+
- Claude's target-list expansion starts only from view-facing changes (views, templates,
|
|
95
|
+
components, CSS) — never from model/migration changes, which usually don't affect rendered
|
|
96
|
+
output and would cause over-selection.
|
|
97
|
+
- A large batch of "added"/"deleted" images clustered under one example usually means a
|
|
98
|
+
Capybara step was inserted or removed in that example, shifting every later step's `NNN`
|
|
99
|
+
sequence number — not many independent regressions.
|
|
100
|
+
- Diff images are linked via artifact URLs rather than embedded in the PR comment, because
|
|
101
|
+
GitHub's Markdown image embedding requires a URL its camo proxy can fetch unauthenticated,
|
|
102
|
+
and artifact URLs require authentication.
|
|
103
|
+
- If a target spec fails on either branch, the shoot job goes red and the compare job is
|
|
104
|
+
skipped, so no diff report is produced. Fix the failing spec first — the workflow does not
|
|
105
|
+
diff against a partial screenshot set. ("Never blocks the PR" refers to the compare job not
|
|
106
|
+
failing the check once it runs, not to tolerating a broken spec run.)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# About the visual-regression-test skill
|
|
2
|
+
|
|
3
|
+
Compares UI changes (views, components, CSS, system specs) before/after using
|
|
4
|
+
capybara-storyboard screenshots and `reg-cli` image diffing, to catch unintended
|
|
5
|
+
visual regressions. Invoked via `/visual-regression-test [ref]`.
|
|
6
|
+
|
|
7
|
+
- "After" screenshots are taken from the main repo's working tree (including
|
|
8
|
+
uncommitted changes).
|
|
9
|
+
- "Before" screenshots are taken from a separate worktree
|
|
10
|
+
(`.claude/worktrees/vrt-baseline`) checked out at the baseline ref (explicit
|
|
11
|
+
argument, HEAD, or the base branch — see `scripts/resolve-baseline.sh` for
|
|
12
|
+
details).
|
|
13
|
+
- The two image sets are diffed with `reg-cli` and rendered as an HTML report.
|
|
14
|
+
The agent verifies whether each diff is intentional before reporting to the
|
|
15
|
+
user.
|
|
16
|
+
|
|
17
|
+
See `SKILL.md` for the full procedure, baseline resolution logic, and target
|
|
18
|
+
spec selection. This README only covers the setup needed to pass step 3b
|
|
19
|
+
(the startup check inside the worktree).
|
|
20
|
+
|
|
21
|
+
## Setup on worktree creation
|
|
22
|
+
|
|
23
|
+
In `SKILL.md` step 3b, whether to proceed with capturing the "before" set is
|
|
24
|
+
decided solely by whether `bundle exec rails runner 'exit 0'` (or an
|
|
25
|
+
equivalent startup command) succeeds inside the worktree.
|
|
26
|
+
|
|
27
|
+
This **assumes the project is configured to automatically provision the
|
|
28
|
+
environment whenever `git worktree add` runs**. Gitignored config files (e.g.
|
|
29
|
+
DB connection settings, credentials) must be copied, and dependencies must be
|
|
30
|
+
installed, by the time the worktree is created — otherwise the startup check
|
|
31
|
+
fails.
|
|
32
|
+
|
|
33
|
+
How this is achieved doesn't matter. A common approach is a `post-checkout`
|
|
34
|
+
hook. Since `post-checkout` fires on both regular checkouts and
|
|
35
|
+
`git worktree add`, distinguish them using the `$1` argument (PREV_HEAD) Git
|
|
36
|
+
passes in: it's the all-zero SHA only for `git worktree add`. Also note that
|
|
37
|
+
if `core.hooksPath` isn't configured, `post-checkout` won't fire at all, even
|
|
38
|
+
from a custom hooks directory.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
#!/bin/sh
|
|
42
|
+
# .githooks/post-checkout
|
|
43
|
+
[ "$1" = "0000000000000000000000000000000000000000" ] || exit 0 # worktree add only
|
|
44
|
+
|
|
45
|
+
main=$(realpath "$(git rev-parse --git-common-dir)/..")
|
|
46
|
+
copy_if_missing() {
|
|
47
|
+
[ -f "$1" ] && return 0
|
|
48
|
+
[ -f "$main/$1" ] || return 0
|
|
49
|
+
mkdir -p "$(dirname "$1")"; cp "$main/$1" "$1"
|
|
50
|
+
}
|
|
51
|
+
copy_if_missing config/database.yml # add any other required config files similarly
|
|
52
|
+
|
|
53
|
+
[ -d vendor/bundle ] || bundle install
|
|
54
|
+
[ -d node_modules ] || npm install
|
|
55
|
+
# if the DB is shared with the main repo, don't call db:prepare or similar
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Which files to copy and which install commands to run varies per project.
|
|
59
|
+
Identify which gitignored files actually cause the startup command to fail
|
|
60
|
+
inside the worktree, and adapt accordingly.
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: visual-regression-test
|
|
3
|
+
disable-model-invocation: true
|
|
4
|
+
description: >-
|
|
5
|
+
Compare before/after UI changes (views, components, CSS, system specs)
|
|
6
|
+
using capybara-storyboard screenshots and reg-cli image diffing, to catch
|
|
7
|
+
unintended visual regressions. The comparison baseline is determined
|
|
8
|
+
automatically from an optional ref argument, the presence of uncommitted
|
|
9
|
+
changes, and the base branch. Invoke explicitly with
|
|
10
|
+
/visual-regression-test [ref].
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Visual regression test (before / after screenshot diff)
|
|
14
|
+
|
|
15
|
+
Catch unintended visual changes introduced by uncommitted edits. Capture capybara-storyboard
|
|
16
|
+
screenshots **twice** — once against the main repo's working tree ("after"), and once against a
|
|
17
|
+
separate worktree checked out at a baseline ref (HEAD, an explicit ref, or the merge-base with
|
|
18
|
+
the base branch — "before") — then diff the two image sets with `reg-cli`. The output is an HTML
|
|
19
|
+
report that highlights which screens changed, so both the agent and the user can confirm whether
|
|
20
|
+
each change was intentional.
|
|
21
|
+
|
|
22
|
+
This differs from "reviewing a screenshot of the current code as-is." A single-snapshot review
|
|
23
|
+
answers "does this screen look right right now?" This skill answers "which screens changed
|
|
24
|
+
compared to before my edit, and was that change intentional?" The latter is what actually catches
|
|
25
|
+
regressions caused by editing a shared partial or layout or CSS. If there's no baseline to compare
|
|
26
|
+
against and the user just wants to look at the current UI, that's the territory of the plain
|
|
27
|
+
`capybara-storyboard` screenshot review, not this skill.
|
|
28
|
+
|
|
29
|
+
Because the "before" capture runs in a separate worktree, it never touches the user's original
|
|
30
|
+
working tree. There's no stash-and-restore step, and therefore no structural risk of losing the
|
|
31
|
+
user's uncommitted work by failing to restore it.
|
|
32
|
+
|
|
33
|
+
## Prerequisites (confirm before starting)
|
|
34
|
+
|
|
35
|
+
This skill assumes capybara-storyboard is already set up (screenshots are captured with
|
|
36
|
+
`SCREENSHOTS=1` and land under `tmp/screenshots/`). Setting it up is out of scope for this skill.
|
|
37
|
+
|
|
38
|
+
- **You must be able to determine what to compare against (the baseline).** Baseline resolution
|
|
39
|
+
is delegated to `scripts/resolve-baseline.sh` (see "Determining the baseline" below and step
|
|
40
|
+
3a). If a ref argument was passed, that ref is always the baseline; with no argument and
|
|
41
|
+
uncommitted changes present, HEAD is the baseline; with no argument and a clean tree, the base
|
|
42
|
+
branch is the baseline. So "the tree is clean" is not itself a reason to abort — no-argument
|
|
43
|
+
plus clean proceeds to a comparison against the base branch (effectively a three-dot diff
|
|
44
|
+
against the merge-base). Only abort when the baseline genuinely can't be resolved
|
|
45
|
+
(`resolve-baseline.sh` exits 2).
|
|
46
|
+
- **reg-cli** runs via `npx reg-cli` (no install needed). Open the report with the platform's
|
|
47
|
+
`open` command (macOS).
|
|
48
|
+
|
|
49
|
+
## Determining the baseline (the "before" worktree's starting ref)
|
|
50
|
+
|
|
51
|
+
"After" is always the main repo's working tree as-is (including any uncommitted changes — the
|
|
52
|
+
after side is never turned into a worktree). Only the ref that the "before" worktree is checked
|
|
53
|
+
out at is decided, using this priority:
|
|
54
|
+
|
|
55
|
+
1. A ref/branch was passed as an argument → use that ref (regardless of uncommitted changes).
|
|
56
|
+
2. No argument, and uncommitted changes exist (`git status --short` is non-empty) → HEAD (the
|
|
57
|
+
original behavior).
|
|
58
|
+
3. No argument, and the tree is clean → the base branch.
|
|
59
|
+
|
|
60
|
+
When the baseline is anything other than HEAD (case 1 or 3), the worktree's actual starting point
|
|
61
|
+
is `git merge-base <baseline> HEAD` (a three-dot diff). For HEAD, no merge-base step is needed —
|
|
62
|
+
use HEAD directly. This logic is centralized in the bundled `scripts/resolve-baseline.sh` (it
|
|
63
|
+
prints the starting ref to stdout on one line, and exits 2 if it can't be resolved). Resolving the
|
|
64
|
+
base branch itself is also handled by that script (roughly in the order
|
|
65
|
+
`github-pr-base-branch` → `vscode-merge-base` → `@{upstream}` → `origin/HEAD`; see
|
|
66
|
+
`resolve-baseline.sh` for the exact order and how each source is handled).
|
|
67
|
+
|
|
68
|
+
### Interpreting the argument
|
|
69
|
+
|
|
70
|
+
Treat the first argument to `/visual-regression-test [ref]` as the baseline ref (any git ref: a
|
|
71
|
+
branch name, tag, SHA, `origin/foo`, etc.). Pass it through as the first argument to
|
|
72
|
+
`resolve-baseline.sh` (pass an empty string if no argument was given).
|
|
73
|
+
|
|
74
|
+
- No argument → baseline is determined automatically (uncommitted changes present = HEAD; clean
|
|
75
|
+
= base branch).
|
|
76
|
+
- One argument → that ref is fixed as the baseline.
|
|
77
|
+
- Two or more arguments → use only the first as the baseline, and briefly tell the user the rest
|
|
78
|
+
were ignored (this skill only supports a single baseline comparison; the range's starting point
|
|
79
|
+
is computed automatically via merge-base, so there's no need to accept both a start and an end).
|
|
80
|
+
- If the given ref can't be resolved, `resolve-baseline.sh` stops with exit 2. Tell the user that
|
|
81
|
+
ref doesn't exist and abort (don't silently fall back to a different ref — respect the explicit
|
|
82
|
+
choice).
|
|
83
|
+
|
|
84
|
+
## Procedure
|
|
85
|
+
|
|
86
|
+
### 1. Identify the target specs
|
|
87
|
+
|
|
88
|
+
Reuse the same target-selection logic as the plain `capybara-storyboard` review, narrowing to the
|
|
89
|
+
system specs that exercise the changed screen(s). Never run the entire suite twice — it's very
|
|
90
|
+
slow, and produces far too large a diff to read.
|
|
91
|
+
|
|
92
|
+
- If `app/views`, `app/components`, or CSS changed, find the system specs that render that
|
|
93
|
+
screen (search by view/component name, controller action, or route). If the call sites are
|
|
94
|
+
countable (roughly 10 or fewer), target those specs directly.
|
|
95
|
+
- If the change is to a layout or a widely shared partial/component, and the search above would
|
|
96
|
+
return most of the suite, don't enumerate every caller. Instead propose a small representative
|
|
97
|
+
sample (roughly 3-6 specs spanning distinct contexts: a signed-out screen, a standard
|
|
98
|
+
authenticated page, an admin/dense-UI page, a narrow-viewport spec if one exists), and **confirm
|
|
99
|
+
with the user** before running.
|
|
100
|
+
- If a system spec itself changed, that spec is the target. Note that if the diff changes the
|
|
101
|
+
spec's own steps, the before/after screenshot sequences won't correspond 1:1 — reg-cli will
|
|
102
|
+
report these as added/removed images, which is expected in this case.
|
|
103
|
+
- If only a single example in a file is relevant, it's fine to scope down to that example (`-e` or
|
|
104
|
+
a line number).
|
|
105
|
+
|
|
106
|
+
Record the exact `rspec` target arguments (files and/or line numbers), since the same command runs
|
|
107
|
+
twice and both runs must match.
|
|
108
|
+
|
|
109
|
+
Note that the target spec may not exist, or may differ, at the baseline ref (resolved as the
|
|
110
|
+
"before" starting point in step 3a). In no-argument mode with uncommitted changes (baseline=HEAD),
|
|
111
|
+
this only applies to newly added specs. In ref-specified or base-branch-comparison mode, the
|
|
112
|
+
target spec itself might be absent from the baseline, or have a different signature
|
|
113
|
+
(describe/example names). Handle that per step 3c and the "images present on only one side are
|
|
114
|
+
new/deleted" note in step 4. For target selection itself, just use the after side (the main repo's
|
|
115
|
+
working tree) as the reference.
|
|
116
|
+
|
|
117
|
+
This target argument works as-is in both the main repo and the worktree described below (the
|
|
118
|
+
worktree is a full checkout of the baseline ref, so its path layout matches the main repo).
|
|
119
|
+
However, if the target includes a spec file that doesn't exist at the baseline ref (a newly added
|
|
120
|
+
spec, or one not yet present when comparing against a base branch), the worktree run will either
|
|
121
|
+
fail to load it or produce an empty "before" set with all images treated as new. This is expected,
|
|
122
|
+
not an error. In base-branch-comparison mode (no argument and clean, or an older explicit ref),
|
|
123
|
+
even when the target spec exists at the baseline its contents may differ, so the screenshot count
|
|
124
|
+
or naming may not match "after". reg-cli will report these as diffs/additions/deletions, which is
|
|
125
|
+
also expected.
|
|
126
|
+
|
|
127
|
+
### 2. Capture the "after" set (main repo's working tree)
|
|
128
|
+
|
|
129
|
+
Capture the after state (with the diff applied) first. This only touches the main repo, so you
|
|
130
|
+
can confirm the target specs actually pass before creating a worktree. If this fails, no worktree
|
|
131
|
+
is created and no work is wasted.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
SCREENSHOTS=1 bundle exec rspec <target specs>
|
|
135
|
+
mkdir -p tmp/vrt && rm -rf tmp/vrt/after
|
|
136
|
+
cp -R tmp/screenshots tmp/vrt/after
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If the target spec **fails**, the screenshot set may be incomplete. Report the failure (the
|
|
140
|
+
failure itself is a finding), and confirm with the user whether it's acceptable to use the partial
|
|
141
|
+
capture as the diff baseline.
|
|
142
|
+
|
|
143
|
+
### 3. Create a worktree at the baseline, verify it boots, and capture "before"
|
|
144
|
+
|
|
145
|
+
#### 3a. Resolve the baseline and create the worktree there
|
|
146
|
+
|
|
147
|
+
Determine the worktree's starting ref using the logic in "Determining the baseline". Pass the
|
|
148
|
+
argument received by this invocation (the baseline ref, or empty if none) straight through to
|
|
149
|
+
`resolve-baseline.sh`.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
WT=.claude/worktrees/vrt-baseline
|
|
153
|
+
# Pass "" if no argument was given. Prints the starting ref/SHA to stdout on one line. Abort on exit 2.
|
|
154
|
+
BEFORE_REF=$(.claude/skills/visual-regression-test/scripts/resolve-baseline.sh "${BASELINE_ARG:-}")
|
|
155
|
+
git worktree add "$WT" "$BEFORE_REF"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`.claude/worktrees/` is an existing empty directory reserved for this skill's use.
|
|
159
|
+
`resolve-baseline.sh` writes the resolved mode (explicit / HEAD / base branch), and — when it's
|
|
160
|
+
not HEAD — the merge-base it applied, to **stderr**. Always mention what was used as the baseline
|
|
161
|
+
in the user-facing report (step 6) (e.g. "compared against the merge-base with the base branch
|
|
162
|
+
origin/release-candidate"), since it changes how the diff should be read.
|
|
163
|
+
|
|
164
|
+
When the starting point is something other than HEAD (an explicit ref or the base branch), the
|
|
165
|
+
worktree is a full checkout of that SHA/branch. Note that the diff shown as "after minus before"
|
|
166
|
+
then includes both the uncommitted changes in the after-side working tree *and* every commit
|
|
167
|
+
between that starting point and HEAD — not just the uncommitted diff, as it would be when the
|
|
168
|
+
baseline is HEAD.
|
|
169
|
+
|
|
170
|
+
#### 3b. Detect environment setup (important — this is what makes the skill portable)
|
|
171
|
+
|
|
172
|
+
Verify whether Rails actually boots inside the worktree by checking boot success itself, rather
|
|
173
|
+
than checking for the presence of any specific hook. Judging by "does it boot" rather than by
|
|
174
|
+
project-specific mechanics keeps this step portable to other projects.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
( cd "$WT" && bundle exec rails runner 'exit 0' )
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
If it boots successfully, treat the config files and dependencies as present (either the
|
|
181
|
+
worktree's automatic setup handled it, or none was needed) and proceed to 3c.
|
|
182
|
+
|
|
183
|
+
If boot fails, abort. Tell the user "this worktree is missing config files/dependencies — you need
|
|
184
|
+
automatic setup on worktree creation (e.g. a post-checkout hook)," and point them at this
|
|
185
|
+
directory's `README.md` (the reference implementation for this skill). Then clean up the worktree
|
|
186
|
+
before finishing.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
git worktree remove --force "$WT"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### 3c. Capture "before" inside the worktree
|
|
193
|
+
|
|
194
|
+
Run the identical rspec command inside the worktree, and collect the results into the main repo's
|
|
195
|
+
`tmp/vrt/before`.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
( cd "$WT" && SCREENSHOTS=1 bundle exec rspec <target specs> )
|
|
199
|
+
rm -rf tmp/vrt/before
|
|
200
|
+
cp -R "$WT/tmp/screenshots" tmp/vrt/before
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Running inside a `( cd "$WT" && ... )` subshell keeps the main repo's current directory clean. If
|
|
204
|
+
the target spec fails, handle it the same way as "after" in step 2 (report the failure, and
|
|
205
|
+
confirm with the user whether to use the partial capture).
|
|
206
|
+
|
|
207
|
+
### 4. Discard the worktree and diff with reg-cli in the main repo
|
|
208
|
+
|
|
209
|
+
Once "before" is captured, the worktree is no longer needed — discard it, then diff in the main
|
|
210
|
+
repo.
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git worktree remove --force "$WT"
|
|
214
|
+
.claude/skills/visual-regression-test/scripts/reg-diff.sh tmp/vrt/after tmp/vrt/before tmp/vrt
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`--force` is used because any untracked `tmp/screenshots` left inside the worktree doesn't matter
|
|
218
|
+
by this point (it was already collected into `before` in step 3c) — just remove it.
|
|
219
|
+
|
|
220
|
+
The bundled helper (`reg-diff.sh`) is equivalent to:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npx reg-cli tmp/vrt/after tmp/vrt/before tmp/vrt/diff \
|
|
224
|
+
--report tmp/vrt/report.html \
|
|
225
|
+
--json tmp/vrt/report.json
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- Argument order matters: **actual (after) first, expected (before) second, diff directory
|
|
229
|
+
third.** Treating after as actual and before as expected frames the report as "what did my diff
|
|
230
|
+
do relative to HEAD."
|
|
231
|
+
- Script exit codes: `0` for no visual change, `1` when a diff is detected (expected and
|
|
232
|
+
**not an error** in this context), `2` for an execution error (bad arguments / missing input
|
|
233
|
+
directory). `1` means "there's a diff, go verify it," not failure.
|
|
234
|
+
- Since both sets ran the same spec, filenames match across the two sets and reg-cli pairs them
|
|
235
|
+
automatically by path. Images present on only one side are reported as added/deleted — this
|
|
236
|
+
happens when the diff changed the spec's own steps.
|
|
237
|
+
|
|
238
|
+
### 5. Verify the detected diffs
|
|
239
|
+
|
|
240
|
+
Read `tmp/vrt/report.json` to get the list of changed/added/deleted images, and for each changed
|
|
241
|
+
screen, read the actual **diff image** (`tmp/vrt/diff/**`) along with the corresponding before/
|
|
242
|
+
after PNGs. Use the filename to determine which action/screen it belongs to
|
|
243
|
+
(`tmp/vrt/before/{Group}/{example}/{NNN_action}.png`). All paths are under the main repo's
|
|
244
|
+
`tmp/vrt`.
|
|
245
|
+
|
|
246
|
+
For each changed image, judge whether the visual change is **intentional**.
|
|
247
|
+
|
|
248
|
+
- Does the visual change match what the code diff was trying to do? (e.g. a color/spacing/label
|
|
249
|
+
change the user made → expected.)
|
|
250
|
+
- Or did an unrelated screen shift — a shared partial/layout edit leaking into pages it shouldn't
|
|
251
|
+
affect, broken layout, overflow, a rendering difference in a component? → report this as a
|
|
252
|
+
regression.
|
|
253
|
+
- Minor sub-pixel/anti-aliasing noise with no meaningful change → note it as likely noise rather
|
|
254
|
+
than a regression. If noise dominates, mention that reg-cli's threshold options
|
|
255
|
+
(`--threshold`, `--enableAntialias`, etc.) can suppress it on a re-run.
|
|
256
|
+
|
|
257
|
+
### 6. Report, then open the HTML report for the user
|
|
258
|
+
|
|
259
|
+
Only open reg-cli's HTML report after finishing the verification in step 5. Opening it earlier
|
|
260
|
+
would show the user diffs the agent hasn't judged yet, breaking the "agent verifies first, then
|
|
261
|
+
presents results to the user" order.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
open tmp/vrt/report.html
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Then return a concise written report:
|
|
268
|
+
|
|
269
|
+
- How many screens changed/were added/were deleted (from `report.json`).
|
|
270
|
+
- For each meaningful change: which screen (spec + action/filename), and your judgment —
|
|
271
|
+
**intentional** (matches the code change) or **possible regression** (what looks wrong and
|
|
272
|
+
where).
|
|
273
|
+
- A clear conclusion: either "all detected changes are consistent with the intended edit," or
|
|
274
|
+
"the following look like unintended regressions: ...".
|
|
275
|
+
- Mention that the HTML report is open in the browser so the user can check it themselves.
|
|
276
|
+
|
|
277
|
+
### 7. Output and cleanup
|
|
278
|
+
|
|
279
|
+
By step 4, the worktree is guaranteed to already be removed via `git worktree remove --force`
|
|
280
|
+
(the same applies if step 3b aborted — it's cleaned up before finishing there too). If for some
|
|
281
|
+
reason execution was interrupted and the worktree was left behind, check with
|
|
282
|
+
`git worktree list` for a stray `.claude/worktrees/vrt-baseline` and remove it with
|
|
283
|
+
`git worktree remove --force <path>`.
|
|
284
|
+
|
|
285
|
+
The worktree shares the same DB (`config/database.yml`) as the main repo. Don't run other tests
|
|
286
|
+
concurrently while this skill is executing (steps 2-4), since DB state can conflict.
|
|
287
|
+
|
|
288
|
+
When the baseline is a base branch or an older ref, that ref's Rails may be out of sync with the
|
|
289
|
+
current shared DB schema (migrations already applied in the main repo), causing the worktree's
|
|
290
|
+
boot check (step 3b) or rspec run to fail. This is an expected failure caused by an outdated
|
|
291
|
+
baseline, and the boot check is what catches it before it goes further. If this happens
|
|
292
|
+
frequently, suggest the user shift the baseline closer to HEAD (e.g. compare against uncommitted
|
|
293
|
+
changes only).
|
|
294
|
+
|
|
295
|
+
This skill's final step is "let the user see the HTML report," so **leave the report and the diff
|
|
296
|
+
images it references (`tmp/vrt/`) in place**. Deleting them here would pull the report out from
|
|
297
|
+
under the user while they're looking at it. Don't clean up on the agent's side.
|
|
298
|
+
|
|
299
|
+
Just mention that these can be removed once no longer needed (leave the decision of when to run
|
|
300
|
+
this to the user):
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
rm -rf tmp/vrt tmp/screenshots
|
|
304
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Diff a before/after set of screenshots using reg-cli.
|
|
3
|
+
# Fixes reg-cli's argument order (actual -> expected -> diff) and report paths
|
|
4
|
+
# to prevent mix-ups on the caller's side.
|
|
5
|
+
#
|
|
6
|
+
# NOTE: does not open the report. The agent verifies report.json / diff images
|
|
7
|
+
# first, and opening it for the user happens afterward (that step is done
|
|
8
|
+
# explicitly in SKILL.md step 6).
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# scripts/reg-diff.sh <after_dir> <before_dir> [out_dir]
|
|
12
|
+
# after_dir : screenshot directory for after (working tree) = reg-cli's actual
|
|
13
|
+
# before_dir : screenshot directory for before (HEAD) = reg-cli's expected
|
|
14
|
+
# out_dir : output destination (default tmp/vrt). diff/report.html/report.json written here
|
|
15
|
+
#
|
|
16
|
+
# Exit codes:
|
|
17
|
+
# 0 ... no diff (no visual change)
|
|
18
|
+
# 1 ... diff detected (expected in this skill's flow, not an error)
|
|
19
|
+
# 2 ... execution error such as invalid arguments or missing input directory
|
|
20
|
+
|
|
21
|
+
set -u
|
|
22
|
+
|
|
23
|
+
after_dir=${1:-}
|
|
24
|
+
before_dir=${2:-}
|
|
25
|
+
out_dir=${3:-tmp/vrt}
|
|
26
|
+
|
|
27
|
+
if [[ -z "$after_dir" || -z "$before_dir" ]]; then
|
|
28
|
+
echo "usage: reg-diff.sh <after_dir> <before_dir> [out_dir]" >&2
|
|
29
|
+
exit 2
|
|
30
|
+
fi
|
|
31
|
+
for d in "$after_dir" "$before_dir"; do
|
|
32
|
+
if [[ ! -d "$d" ]]; then
|
|
33
|
+
echo "error: directory not found: $d" >&2
|
|
34
|
+
exit 2
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
diff_dir="$out_dir/diff"
|
|
39
|
+
report_html="$out_dir/report.html"
|
|
40
|
+
report_json="$out_dir/report.json"
|
|
41
|
+
mkdir -p "$diff_dir"
|
|
42
|
+
|
|
43
|
+
# Pass args in the order actual(after) -> expected(before) -> diff.
|
|
44
|
+
npx reg-cli "$after_dir" "$before_dir" "$diff_dir" \
|
|
45
|
+
--report "$report_html" \
|
|
46
|
+
--json "$report_json"
|
|
47
|
+
reg_status=$?
|
|
48
|
+
|
|
49
|
+
# reg-cli exits non-zero (1) when a diff is detected. That's expected, so don't abort here.
|
|
50
|
+
# Don't open the report (opened in step 6, after verification).
|
|
51
|
+
echo "---"
|
|
52
|
+
echo "reg-cli exit=$reg_status (0=no diff, 1=diff detected)"
|
|
53
|
+
echo "report.html: $report_html # open this after verification to check"
|
|
54
|
+
echo "report.json: $report_json"
|
|
55
|
+
echo "diff images: $diff_dir"
|
|
56
|
+
|
|
57
|
+
exit "$reg_status"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve the starting ref for the "before" worktree according to a priority order.
|
|
3
|
+
#
|
|
4
|
+
# Priority:
|
|
5
|
+
# 1. <ref> argument given -> use that ref as baseline
|
|
6
|
+
# 2. no argument & uncommitted changes present -> HEAD as baseline (current behavior)
|
|
7
|
+
# 3. no argument & clean tree -> base branch as baseline
|
|
8
|
+
#
|
|
9
|
+
# If baseline is anything other than HEAD, the worktree starting point is
|
|
10
|
+
# merge-base <baseline> HEAD (equivalent to a three-dot diff).
|
|
11
|
+
# If it's HEAD, no merge-base is needed and "HEAD" is returned as-is.
|
|
12
|
+
#
|
|
13
|
+
# stdout : one line with the ref/SHA to pass to worktree add
|
|
14
|
+
# stderr : human-readable explanation of the resolution
|
|
15
|
+
# exit : 0=resolved, 2=execution error
|
|
16
|
+
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
arg_ref=${1:-}
|
|
20
|
+
|
|
21
|
+
log() { echo "$@" >&2; }
|
|
22
|
+
|
|
23
|
+
# Check whether a ref actually exists (existence check only, no side effects)
|
|
24
|
+
ref_exists() { git rev-parse --verify --quiet "$1" >/dev/null; }
|
|
25
|
+
|
|
26
|
+
# Resolve the base branch (order follows github.com/aki77/claude-code-review as reference)
|
|
27
|
+
resolve_base_branch() {
|
|
28
|
+
local cur cfg branch cand upstream sym
|
|
29
|
+
cur=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
30
|
+
|
|
31
|
+
# 1. branch.<cur>.github-pr-base-branch
|
|
32
|
+
# NOTE: the value may be in "owner#repo#branch" form (VSCode GitHub PR extension).
|
|
33
|
+
# Extract the last '#' segment as the branch name and prefer origin/.
|
|
34
|
+
# The raw value can't be passed to rev-parse as-is.
|
|
35
|
+
cfg=$(git config "branch.$cur.github-pr-base-branch" 2>/dev/null)
|
|
36
|
+
if [ -n "$cfg" ]; then
|
|
37
|
+
branch=${cfg##*#}
|
|
38
|
+
for cand in "origin/$branch" "$branch" "$cfg"; do
|
|
39
|
+
if ref_exists "$cand"; then echo "$cand"; return 0; fi
|
|
40
|
+
done
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# 2. branch.<cur>.vscode-merge-base (e.g. origin/release-candidate; usable as-is)
|
|
44
|
+
cfg=$(git config "branch.$cur.vscode-merge-base" 2>/dev/null)
|
|
45
|
+
if [ -n "$cfg" ] && ref_exists "$cfg"; then
|
|
46
|
+
echo "$cfg"; return 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# 3. @{upstream}
|
|
50
|
+
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null)
|
|
51
|
+
if [ -n "$upstream" ]; then
|
|
52
|
+
echo "$upstream"; return 0
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# 4. origin/HEAD (e.g. refs/remotes/origin/release-candidate)
|
|
56
|
+
sym=$(git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null)
|
|
57
|
+
if [ -n "$sym" ]; then
|
|
58
|
+
echo "${sym#refs/remotes/}"; return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
emit_mergebase() {
|
|
65
|
+
local baseline=$1 mb
|
|
66
|
+
if ! ref_exists "$baseline"; then
|
|
67
|
+
log "error: baseline ref not found: $baseline"
|
|
68
|
+
exit 2
|
|
69
|
+
fi
|
|
70
|
+
mb=$(git merge-base "$baseline" HEAD 2>/dev/null)
|
|
71
|
+
if [ -z "$mb" ]; then
|
|
72
|
+
log "error: no merge-base between $baseline and HEAD"
|
|
73
|
+
exit 2
|
|
74
|
+
fi
|
|
75
|
+
log "baseline: '$baseline' -> merge-base $mb"
|
|
76
|
+
echo "$mb"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# --- Priority resolution ---
|
|
80
|
+
if [ -n "$arg_ref" ]; then
|
|
81
|
+
log "baseline mode: explicit ref '$arg_ref'"
|
|
82
|
+
emit_mergebase "$arg_ref"
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
if [ -n "$(git status --short)" ]; then
|
|
87
|
+
log "baseline mode: uncommitted changes present -> HEAD"
|
|
88
|
+
echo "HEAD"
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
log "baseline mode: clean tree -> base branch"
|
|
93
|
+
base=$(resolve_base_branch) || { log "error: could not resolve base branch"; exit 2; }
|
|
94
|
+
log "resolved base branch: $base"
|
|
95
|
+
emit_mergebase "$base"
|
|
96
|
+
exit 0
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capybara-storyboard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aki
|
|
@@ -38,6 +38,7 @@ files:
|
|
|
38
38
|
- README.md
|
|
39
39
|
- Rakefile
|
|
40
40
|
- docs/github-actions.md
|
|
41
|
+
- docs/visual-regression.md
|
|
41
42
|
- lib/capybara/storyboard.rb
|
|
42
43
|
- lib/capybara/storyboard/configuration.rb
|
|
43
44
|
- lib/capybara/storyboard/context.rb
|
|
@@ -49,6 +50,10 @@ files:
|
|
|
49
50
|
- lib/capybara/storyboard/version.rb
|
|
50
51
|
- sig/capybara/storyboard.rbs
|
|
51
52
|
- skills/capybara-storyboard/SKILL.md
|
|
53
|
+
- skills/visual-regression-test/README.md
|
|
54
|
+
- skills/visual-regression-test/SKILL.md
|
|
55
|
+
- skills/visual-regression-test/scripts/reg-diff.sh
|
|
56
|
+
- skills/visual-regression-test/scripts/resolve-baseline.sh
|
|
52
57
|
homepage: https://github.com/aki77/capybara-storyboard
|
|
53
58
|
licenses:
|
|
54
59
|
- MIT
|