Skip to content
Pages

Repeatable runs

A changed screenshot only means something if two runs of the same code draw the same pixels. Most of Ezu's work goes into making that true.

After reading this page, you will know what Ezu holds still, and what to do when a screen changes on its own.

What a run holds still

Time. Time stands still during each step and moves 3 seconds between steps, starting from the config's moment, in the app and in the browser. "Active 2m ago" then depends on how many steps passed, never on the day or the machine's speed. The browser uses Playwright's clock; the app reads Ezu's clock file through repeatable.rb. Page timers also pause while screenshots are taken, so a toast that dismisses itself is either in every run or in none.

Randomness. Every line of code that asks for randomness gets its own seeded sequence, so tokens, avatar colours and share links repeat even when threads ask in a different order. In the browser, Math.random is seeded.

Ports. Each slot has a fixed port, because share links and QR codes contain it.

The outside world. Every request that leaves the machine is aborted. Third-party scripts and fonts load differently each run.

Motion and pointer. The browser asks for reduced motion and Playwright disables CSS animations. Before each screenshot, the mouse moves to the corner, the page scrolls to the top, and lazy images load.

Fonts. Ezu waits until no font is loading, across frames, because fonts split by unicode-range start loading late.

Settling. A screenshot counts only once two in a row are identical, as Playwright's own toHaveScreenshot does. A page that never settles is flagged as an issue.

Isolation. Each slot has its own database copy, server, clock and browser, so no slot can see another's clicks.

When a screen changes on its own

If check fails without a code change, something in the app varies between runs: a date not read through Time.now, a random value from a source Ezu doesn't seed, an animation that ignores reduced motion, a third-party widget. Fix the cause. Raising a tolerance only hides the next real change.

Pin anything the app prints that changes per commit, such as a version in the footer, through the adapter's env.