|
| 1 | +# Percy snapshots |
| 2 | + |
| 3 | +This repo runs visual regression tests via [Percy](https://percy.io/). Three workflows orchestrate it: |
| 4 | + |
| 5 | +- `.github/workflows/percy-pr.yaml` — PRs from this repo targeting `main` |
| 6 | +- `.github/workflows/percy-fork-pr.yaml` — PRs from forks targeting `main` (uses `pull_request_target` with an approval gate) |
| 7 | +- `.github/workflows/percy-baseline.yaml` — pushes to `main` (refreshes the baseline that PRs compare against) |
| 8 | + |
| 9 | +The shared gate logic lives in `.github/actions/percy-gate/action.yml`. |
| 10 | + |
| 11 | +## When Percy runs |
| 12 | + |
| 13 | +For PRs (both internal and fork) the `decide` job evaluates, in order: |
| 14 | + |
| 15 | +1. **`run-percy` label present on the PR?** → RUN. |
| 16 | +2. **Author is `dependabot[bot]` or `renovate[bot]`?** → SKIP (bots must use the label to opt in). |
| 17 | +3. **Diff touches a watched path?** → RUN. |
| 18 | +4. **Otherwise** → SKIP. |
| 19 | + |
| 20 | +For pushes to `main` (baseline): |
| 21 | + |
| 22 | +1. **Workflow manually dispatched?** → RUN. |
| 23 | +2. **Push diff touches a watched path?** → RUN. |
| 24 | +3. **Otherwise** → SKIP. |
| 25 | + |
| 26 | +When the gate decides to skip, the `snapshot` job is skipped but the workflow exits successfully — branch protection (which treats `skipped` as `success` for required checks) is unaffected. |
| 27 | + |
| 28 | +## Watched paths |
| 29 | + |
| 30 | +Defined once in `.github/actions/percy-gate/action.yml`: |
| 31 | + |
| 32 | +- `static/sass/**` |
| 33 | +- `static/js/**` |
| 34 | +- `templates/**` |
| 35 | +- `navigation.yaml` |
| 36 | +- `secondary-navigation.yaml` |
| 37 | +- `snapshots.js` |
| 38 | +- `test-links.yaml` |
| 39 | +- `package.json` |
| 40 | +- `yarn.lock` |
| 41 | + |
| 42 | +To extend the list, edit the `filters:` block in `.github/actions/percy-gate/action.yml`. The change applies to all three workflows automatically. |
| 43 | + |
| 44 | +## How to force a Percy run |
| 45 | + |
| 46 | +### On a PR |
| 47 | + |
| 48 | +Add the `run-percy` label. Adding the label fires a `labeled` event, the gate re-evaluates, and the snapshot job runs. |
| 49 | + |
| 50 | +Reach for this when: |
| 51 | + |
| 52 | +- The PR changes visuals via a path we don't watch (e.g. a Python view that swaps template variables). |
| 53 | +- A Renovate or Dependabot PR bumps a visual-affecting dependency (e.g. `vanilla-framework`) and you want a snapshot. |
| 54 | +- You want a one-off sanity-check snapshot on any PR. |
| 55 | + |
| 56 | +### On `main` (refresh the baseline manually) |
| 57 | + |
| 58 | +Go to **Actions → Update Percy Baseline → Run workflow**, select `main`, click Run. |
| 59 | + |
| 60 | +Reach for this when: |
| 61 | + |
| 62 | +- A merged PR's change wasn't caught by the path filter and visual drift later surfaces in production. |
| 63 | +- You want a fresh baseline before a release. |
| 64 | + |
| 65 | +## Rapid pushes are de-duplicated |
| 66 | + |
| 67 | +PR workflows declare a `concurrency` group with `cancel-in-progress: true`, so pushing several commits in quick succession only completes the latest run — earlier in-flight Percy runs for the same PR are cancelled. The baseline workflow does not cancel itself (every `main` push completes its baseline independently). |
| 68 | + |
| 69 | +## Troubleshooting |
| 70 | + |
| 71 | +**"Percy didn't run on my PR with a CSS change."** |
| 72 | +Check the watched-paths list above. If the path is genuinely visual-affecting and not covered, add it to `.github/actions/percy-gate/action.yml`. For a one-off run, add the `run-percy` label. |
| 73 | + |
| 74 | +**"The `Take Percy snapshots` check shows as skipped."** |
| 75 | +Expected when the gate decided to skip. The `Decide whether to run Percy` job ran to completion and reported success — branch protection is satisfied. |
| 76 | + |
| 77 | +**"Percy ran on a PR that didn't change visuals."** |
| 78 | +Check whether the diff touches any watched path — `package.json` and `yarn.lock` will match any dependency bump. Bots are blocked by default, but a human PR touching these triggers a run. |
0 commit comments