Docs / Testing and review
Reviewing a pull request's screenshots
Approve or reject each screenshot a pull request changed, one keystroke at a time, and see where the review stands. A review records a decision. It never moves @latest or any other channel.
The review workspace
Open the pull request's gallery in the console (the link in the pull-request comment goes there) and choose Review changes. The workspace lists every screenshot that is changed, added or removed; unchanged screenshots need no decision and are left out.
- Left: the screenshots to review, each marked approved, rejected or unreviewed.
- Centre: the same graded compare view as the asset page, with eight ways to look at one change (changes, side by side, wipe, flicker, onion skin, heat, structure, pixels). A non-public screenshot cannot be shown on this page without a credential; open its asset page to see it.
- Bottom: Approve, Reject and Undo, which stay on screen as you scroll.
Your marks are drafts until you choose Submit review. Drafts are kept in your browser, so reloading the page keeps them. Submitting sends every draft as one review, with an optional summary. If the pull request pushed new screenshots after you opened the page, the submission is refused for the screenshots that changed, and the page says to reload.
Keyboard shortcuts
| Key | Does |
|---|---|
| Y | Approve, then go to the next screenshot nobody has decided |
| N | Reject, then go to the next screenshot nobody has decided |
| ↓ / ↑ | Next / previous screenshot |
| ⌘Z / Ctrl+Z | Undo the last mark |
| ? | Show the shortcuts |
A chip such as 3 / 12 reviewed counts your drafts and the decisions already recorded.
Review state
For its newest push, a pull request's review is in one of four states:
- approved — every changed, added and removed screenshot is approved.
- rejected — at least one is rejected.
- pending — none is rejected, and at least one has no decision yet.
- no-changes — nothing in the push needs a decision.
A screenshot whose every change is one your flake ledger ignores needs no decision by default: its bytes are new, but all of the change is known noise. An admin can make those need an approval too, on the project's Ignored page (Ignored changes in review).
One rejected screenshot makes the whole review a rejecting one. A later decision on the same screenshot replaces an earlier one.
New pushes keep earlier approvals
A decision is about the exact bytes you looked at, including a screenshot's dark or other themes. When you push again to the same pull request, every screenshot whose bytes did not change keeps its decision, so fixing a typo on one page does not send the whole pull request back for review. An approval also carries over when the bytes changed but the change is the same one, such as a header that moved the same 4 pixels again. Anything else whose bytes changed shows as unreviewed again, and a rejection only stays with the exact bytes it was made on.
The screenery check on GitHub
On a pull request, Screenery also reports the review state to GitHub as a check named screenery, on the pull request's newest commit. It appears in the pull request's checks list next to your CI jobs:
| Review state | The check |
|---|---|
| no-changes | Success: no visual changes |
| approved | Success: every change approved |
| pending | Action required: changes waiting for review |
| rejected | Failure: a change was rejected |
The check is posted when a push finishes and updated every time someone submits a review, so approving the last change turns it green without a new commit. A new push starts a new check on the new commit, and screenshots whose bytes did not change keep their decisions. Its Details link opens the review workspace. The check lists screenshots by name and counts only; it never shows an image, because anyone who can read the repository can read a check.
When one repository feeds more than one Screenery project, each project's check is named screenery/<project> instead, so they do not overwrite each other. The project's Access screen in the console shows the exact name under PR check.
The check needs three things:
- The push came from a pull_request workflow run using screenery/push (or screenery push) with permissions: id-token: write. A push with a stored SCREENERY_TOKEN gets no check, because Screenery cannot prove which pull request it came from.
- The Screenery GitHub App installation on your account has the Checks: write permission. If your installation predates it, GitHub emails the account's owners and asks one of them to review and accept the updated permissions on the installation's settings page (for a personal account, Settings → Applications → Installed GitHub Apps → Screenery; for an organization, the organization's Settings → GitHub Apps → Screenery). Until then, pull requests get no check and everything else works as before.
- The project's PR check setting on the Access screen is on, which is the default. Turning it off stops new checks; a check already on a commit stays as it was.
Requiring approval before merge
To block merging until every visual change is approved, make the check required in the repository's branch protection:
- Open a pull request and let Screenery post the check once. GitHub only offers checks it has seen recently.
- In the repository on GitHub, go to Settings → Branches and edit the rule for your default branch (or add one), or use Settings → Rules → Rulesets.
- Turn on Require status checks to pass before merging, search for screenery (or screenery/<project>) and add it. In a ruleset, choose the Screenery app as the source of the check if GitHub asks.
- Save. From then on, a pull request with an unreviewed or rejected change cannot be merged until someone approves it in Screenery.
Two things to know before you require it. A pull request whose push never reaches Screenery (a fork, a workflow that skipped the push, a job without id-token: write) never gets the check, so GitHub waits for it. And if you later bind a second project to the same repository, the check's name changes to screenery/<project>: update the required check to match.
The check is the gate a person can clear. If you would rather the CI job itself fail on visual changes, use the visual-diff gate as well or instead.
Who can review
Reviewing needs the publisher role or higher in the organization, the same role that may promote a channel. A viewer can see the gallery and the review state, and can comment, but cannot approve or reject. Someone viewing a public project's gallery without signing in can see the screenshots but not review them. A publish or read token cannot review either: a review is always attributed to a person.
Reviewing with an agent
Review with Claude in the workspace opens Claude Code with a prompt that names the pull request, the project and the two tools it needs from the Screenery MCP server: screenery_gallery to read every changed screenshot and what is already decided, and screenery_review to record one decision per screenshot. Copy prompt copies the same prompt for any other agent. Nothing is sent from the page.
A review records who submitted it and how: from the console, the CLI, or an agent's MCP connection, with the agent's name when it gives one. The gallery and the review state show it, so you can tell your own decision from an agent's.
What a review does not do
- It moves no channel. Approving does not change @latest. Merging to your default branch and your promotion rules are still what move it.
- It does not change a baseline. The next pull request is still compared with the channel its base branch points at.
- It does not block a merge on its own. It updates the screenery check, which blocks merging only if you make it required. To fail a CI job on visual changes, use the visual-diff gate.