Skip to content

Post a before/after screenshot gallery on every stylesheet PR - #52

Closed
Fivell wants to merge 8 commits into
masterfrom
ci/screens
Closed

Fivell wants to merge 8 commits into
masterfrom
ci/screens

Conversation

@Fivell

@Fivell Fivell commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Reviewing a change to this theme means opening an admin and looking at it, which is slow enough that in practice it does not happen. This shoots the looking.

rake screens generates a throwaway Rails app with ActiveAdmin installed, compiles the theme twice — once from the PR's base, once from its head — and takes the same set of screenshots against each. The PR then gets one comment with a before/after row per scenario.

What a reviewer sees

One section per scenario that the PR actually changes, each with a line saying what to look at:

menu-light-theme

The whole point of the menu variables: a light panel must stay readable, including the hovered item and the current one.

before after
… …

Scenarios the PR leaves untouched are collapsed into a <details> list rather than padding the comment — decided by digest, since both revisions are shot on the same runner in the same job.

Design

The dummy app supplies markup only. The theme is compiled with sassc and swapped into the page at screenshot time instead of being wired through the app's asset pipeline. "Before" and "after" then differ by exactly one stylesheet, switching revisions costs a recompile rather than a rebuild, and sassc-rails — plus any opinion about how a host project builds assets — stays out of it. ActiveAdmin is taken as ~> 3.0, so the gallery follows the latest 3.x without anyone remembering to bump it.

Scenarios are data. test/screens/scenarios.rb is a list of hashes: a name, the variable overrides to compile with, a path, what to hover or click, what to crop. Adding a case is adding a hash; there is no code to touch.

{
  name: "menu-dark-panel",
  why: "A dark panel must keep its hover feedback and its submenu marker.",
  overrides: '$skinMenuPanelColor: #222222;',
  path: "/admin/posts",
  steps: [[:hover, "#system > a"]],
  crop: ["#header", "#system > ul"],
}

The fifteen here cover the menu and its dropdowns, the title bar, the index table and its tool row, pagination, the filter sidebar, forms and show pages — under default, light-panel, dark-panel, split-colour and enlarged configurations.

No pixel comparison against golden files. Font rendering differs between machines, so a byte-exact baseline would mostly produce false alarms and train everyone to ignore the job. Digests only decide whether a scenario is shown or folded away; a human looks at the rest.

Why two workflows

Both open theme PRs come from forks, and a pull_request run on a fork gets a read-only token and no secrets, so it cannot comment. Screens therefore runs in the PR's own context and only leaves an artifact; Screens comment picks it up on workflow_run with the repository's permissions and does the publishing. This is the standard split for fork-safe PR feedback.

Images are published as assets on a screens prerelease. Release assets are not part of the git history, so nothing is committed to a branch and a clone does not carry them.

Ferrum drives Chrome over CDP from Ruby, so CI needs no Node toolchain — only the browser the runner already has.

Checked locally

Full run against a real ActiveAdmin 3.5.2 admin:

$ bundle exec rake "screens[origin/master]"
dummy: ready at …/tmp/screens-dummy
shoot: 15/15 scenarios      # after
shoot: 15/15 scenarios      # before

It has already paid for itself once: shooting #51 against a real admin showed $skinMenuPillTextColor defaulting to #ffffff independently of $skinMenuTextColor, so light-theming the menu left the top-level pill label white on a light fill. That is a defect reading the diff did not catch, and it is fixed on that branch.

On this PR the gallery should report no changed scenarios — the theme is untouched here, so it doubles as a smoke test of the pipeline.

One thing this PR cannot prove about itself

Screens is green here and uploads its artifact — 30 PNGs, 15 scenarios twice.
Screens comment cannot run yet: GitHub only triggers workflow_run from the
workflow file as it exists on the default branch, so the comment half starts
working on the first PR after this merges, and this PR will not comment on
itself. The artifact on the run above is the same input that half consumes, so
it can be inspected now if you want to see the images before merging.

Follow-up

docs/pr-47-fixes/preview.rb on #51 is an earlier, hand-rolled version of this against static markup. Once both land it can go, with the README there pointing at rake screens instead.

Fivell added 8 commits October 2, 2026 11:00
Reviewing a change to this theme means opening an admin and looking at it,
which is slow enough that it does not happen. This shoots the looking.

`rake screens` generates a throwaway Rails app with ActiveAdmin installed,
compiles the theme twice — once from the pull request's base, once from its
head — and takes the same set of screenshots against each. A pull request
then gets one comment with a before/after row per scenario, so the whole
visual effect of a change is on the page without checking anything out.

Three decisions worth stating:

* The dummy app supplies markup only. The theme is compiled with sassc and
  swapped into the page at screenshot time rather than wired through the
  app's asset pipeline, so "before" and "after" differ by exactly one
  stylesheet and switching revisions costs a recompile instead of a rebuild.
  It also keeps sassc-rails and any opinion about host asset pipelines out
  of this. ActiveAdmin is taken as `~> 3.0`, so the gallery tracks the
  latest 3.x without anyone remembering to bump it.

* Scenarios are data (test/screens/scenarios.rb): a name, the variable
  overrides to compile with, a path, what to hover or click, and what to
  crop. Adding a case is adding a hash. The fifteen here cover the menu and
  its dropdowns, the title bar, the index table and its tool row,
  pagination, the filter sidebar, forms and show pages — under light,
  dark-panel, light-panel and enlarged configurations.

* No pixel comparison against golden files. Font rendering differs between
  machines, so a byte-exact baseline would mostly produce false alarms and
  teach everyone to ignore it. Identical digests only decide whether a
  scenario is listed or folded away as untouched; a human looks at the rest.

Two workflows rather than one because both open theme pull requests come
from forks: Screens runs in the pull request's context, where the token is
read-only, and leaves an artifact; Screens comment picks it up afterwards
with the repository's own permissions. Images are published as assets on a
prerelease, so they stay out of the git history — nothing is committed to a
branch and a clone does not carry them.

Ferrum drives Chrome over CDP from Ruby, so CI needs no Node toolchain, only
the browser the runner already has.
ruby/setup-ruby with bundler-cache writes .bundle/config into the repository
with frozen and a vendored path. The generated app and the bootstrap Gemfile
live under the repository, so bundler walked up and applied a lockfile that
does not describe them: CI could not resolve rails for the dummy app at all.
ruby/setup-ruby exports several; missing one left the bootstrap Gemfile
resolving in local mode against the repository's lockfile and failing to
find rails at all.
Pinning Rails 7.2 meant installing a second Rails and then fighting over
which one the shim resolved to. Rails 8 runs ActiveAdmin 3.5.2 fine; the one
thing it stopped doing is writing app/assets/config/manifest.js for
--asset-pipeline=sprockets, which sprockets-rails refuses to boot without, so
the generator writes it. No version pin left to drift.
ruby/setup-ruby writes .bundle/config into the working directory and bundler
walks up from wherever it runs, so an app generated under tmp/ inherited a
lockfile that does not describe it and resolved in local mode — CI could not
install the app's own gems at all.
Clearing BUNDLE_* by hand missed whatever ruby/setup-ruby adds, and the
symptom was bundler resolving the dummy app in local mode: it refused to
fetch the app's gems and reported them as not installed. with_unbundled_env
restores the environment as it was before bundler touched it, which is what
this needs and does not have to be kept in step with the CI action.
Gem.bin_path in the parent found the railties vendored for this gem, which is
not on the load path once with_unbundled_env drops the bundle, so exe/rails
could not require rails/cli. Install Rails as an ordinary gem and let the
unbundled child resolve it.
The runner is slower than a developer machine and timed out on one page,
which failed the job and cost the whole gallery. Raise Ferrum's timeouts, and
report a scenario that could not be shot in the comment instead of aborting —
only an entirely empty run is a failure now.
@Fivell

Fivell commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Closing: the CI half is not worth what it costs here. The screenshots are just as useful generated locally, and getting workflow_run + release assets working took six iterations of bundler fights for something a single ruby screens.rb does on a laptop.

The tooling lives outside the repository now and shoots per commit rather than just base-vs-head, which is more useful for reviewing a branch anyway.

What this PR did establish is worth keeping in mind: shooting against a real ActiveAdmin admin, rather than hand-written markup, immediately caught $skinMenuPillTextColor defaulting to #ffffff independently of $skinMenuTextColor — fixed in #51.

@Fivell Fivell closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant