Skip to content
Pages

Architecture

Ezu is a Ruby gem with an executable, about 1,900 lines including the viewer. Its core knows nothing about any web framework; adapters do.

After reading this page, you will know how a run flows through the code and what each file does.

The pieces

exe/ezu → cli.rb            map | check | approve
project.rb                  loads ezu/config.rb and ezu/flows/*.rb (the DSL)
adapters/rails.rb           boots a Rails app; rails/ has the runner scripts
  rails/repeatable.rb       preloaded into the app: frozen clock, seeded randomness
adapters/command.rb         the same interface through shell commands
clock.rb                    the stepped clock, written to a file the app reads
recorder.rb                 one Playwright and Chromium per slot; runs every flow
walk.rb                     one flow in one slot: steps, waits, screenshots
model.rb                    routes + walks → pages, states, flows; the rules
baseline.rb                 the approved fingerprints, or the previous run
comparison.rb               new / changed / same / gone, pixel diffs in Chromium
viewer/index.html           static; reads ezu-data.js and nothing else

A run, in order

  1. cli.rb loads the project and makes one adapter for preparing plus one per slot. A slot is a screen size in a locale; ports are the base port plus the slot's place in the full list.
  2. The preparing adapter makes the throwaway copy and reads routes and translations.
  3. One thread per slot copies the prepared app, runs the seed, starts the server, and hands a Recorder its URL.
  4. The recorder opens a browser context per flow, with the slot's viewport, locale, time zone, clock, cookies, blocked URLs and seeded Math.random, then calls the flow with a Walk.
  5. Each walk.step ticks the clock, runs the block, waits for the page to settle, fails on bad responses, and captures the screenshots.
  6. When every slot is done, Model matches each step's URL to a route, groups steps into states on pages, and enforces the rules: one main state per page, no flow on an unreachable page.
  7. Comparison compares with the approved baseline, or the previous run, and writes diffs for changed screenshots.
  8. cli.rb writes ezu.json, ezu-data.js and the viewer to tmp/ezu.next/, then swaps it into tmp/ezu/. A failed run never replaces the last good map.

The output

tmp/ezu/
  index.html          the viewer
  ezu-data.js         window.EZU = { … }, the same data as ezu.json
  ezu.json            format 2: pages, states, flows, counts, comparison
  screenshots/        <flow>-<step>-<slot>.png, plus -dark.png
  previous/ changes/  the baseline's images and pink diffs, for changed states

The viewer depends on ezu.json's shape and nothing else, so another reader, such as a Rails app that keeps history across runs, could use the same file.

Tech stack

Part What
Language Ruby 3.3 or later (developed on 4.0)
Browser playwright-ruby-client 1.64.0 driving Node playwright 1.64.1 and its Chromium, both pinned
Pixel diff In Chromium, on an OffscreenCanvas; identical files skip it
Approvals SHA-256 fingerprints in JSON
Viewer One HTML file, plain JavaScript and CSS, no build step
Tests Minitest through bin/test: unit tests plus a fixture app mapped twice end to end