| name | visual-check |
|---|---|
| description | How to visually verify UI changes using bin/visual-check. Use when making any UI/frontend change, or when asked to check how the app looks or behaves in the browser. |
| user-invocable | false |
bin/visual-check is a CLI that drives a real browser (Capybara + Selenium
headless Chrome) against the running bin/dev server. Use it to verify
that UI changes look and behave correctly before declaring a frontend task
complete.
- You edited a ViewComponent, ERB template, or Tailwind class.
- You changed a flow (form, redirect path, multi-step interaction).
- You are reproducing or fixing a visual bug.
- Someone asked "does this look right?"
bin/dev must be running in another terminal. bin/visual-check does not
start the server for you. It assumes the dev server is on port 3000
(override with --port).
Current dev server state:
!curl -sfI --max-time 2 http://localhost:3000/up >/dev/null 2>&1 && echo "dev server reachable on :3000" || echo "dev server NOT reachable on :3000. Start bin/dev yourself in the background, then poll /up before invoking bin/visual-check."
If the server is not reachable, start it yourself. Run bin/dev in the
background, then poll http://localhost:3000/up until it answers before
invoking bin/visual-check. Don't ask the user to start it.
Pipe the script into bin/visual-check via a heredoc. This avoids bash
escaping issues with quotes, #{}, backticks, and $.
bin/visual-check --as vincent@example.com <<'RUBY'
visit posts_path
screenshot "posts-index"
click_on "New post"
screenshot "new-post-form"
RUBYAlways use a single-quoted heredoc delimiter (<<'RUBY') so the shell
does not interpolate your script.
Flags:
--as EMAIL: sign in as this dev-stub user (e.g.vincent@example.com). The CLI navigates to/dev/sign_in_as?user=…, the dev-only route the app uses to set the current user for subsequent requests.--account NAME: scope the dev sign-in to this account, for multi-tenant apps. Passed through to/dev/sign_in_asas theaccountquery param.--viewport WxH: default1440x900.--headed: show the browser window (default is headless).--port N: override port resolution.--run-id ID: override the generated run id.
Inside the script you have:
- Capybara DSL:
visit,click_on,fill_in,find,within,have_content,page, etc. Same vocabulary astest/system/. - Rails models:
User.find_by(...),Post.last, etc. The CLI shares the dev-server's database. - URL helpers:
posts_path,post_path(post), etc. - Run-level verbs:
screenshot(name)writes a PNG totmp/visual-check/<run-id>/<name>.png.snapshot(name)writes HTML totmp/visual-check/<run-id>/<name>.html.
- Locals (when
--asis set):current_user: theUserrecord (matched byemail).
Names for screenshot/snapshot must match [a-z0-9][a-z0-9-_]*. This
prevents ../etc/passwd-style escapes.
Before/after a click:
bin/visual-check --as vincent@example.com <<'RUBY'
visit post_path(Post.last)
screenshot "post-before"
click_on "Publish"
screenshot "post-after"
RUBYWalk a flow with one login:
bin/visual-check --as vincent@example.com <<'RUBY'
visit new_post_path
fill_in "Title", with: "Hello world"
fill_in "Body", with: "First post"
click_on "Create Post"
screenshot "post-created"
RUBYReproduce a bug:
bin/visual-check --as vincent@example.com <<'RUBY'
visit dashboard_path
screenshot "before-bug"
# steps that trigger the bug
screenshot "after-bug"
RUBYOn any script exception, the CLI writes:
failure.png: screenshot of the dying state.failure.html: the current DOM.meta.json: includesexit_code: 1and anerrorobject (class, message, backtrace).
All under tmp/visual-check/<run-id>/. Read the failure PNG before
assuming the script is wrong. Often the page loaded something unexpected
(a flash error, an unauthenticated redirect).
After running, always:
- Include the run directory path in your response so the user can look at the artifacts.
- Summarise what you observed, not just "ran successfully." Example:
"The new published badge renders top-right on the post detail page;
both default and headed runs look correct. Run:
tmp/visual-check/20260508-141522-a3f/."
- Not a replacement for system tests. Tests in
test/system/still own assertion-level regression coverage. - Not safe against staging or production. The CLI talks to
http://localhost:<port>only. - Not a pixel-diff tool. Artifacts are for human/agent interpretation.