Visual regression


Visual regression compares a screenshot of the current page against a stored baseline and records the result as pass or fail on the run. Use it to guard against unintended visual changes on a page you have already validated.

Running it in CI? See Visual regression in CI for the baseline-cache pattern, Job Summary snippet and the two options to publish the HTML report.

#Take a checkpoint

Call visualCheckpoint($label) at any point in a step where you want to compare against a baseline. The label identifies the checkpoint across runs.

Copied!
1$this
2->it('the checkout addresses step is unchanged', function () use ($frontOfficeCheckoutPage) {
3 $frontOfficeCheckoutPage->confirmAddresses();
4 
5 $this->visualCheckpoint('checkout-tunnel-addresses');
6});

The first time this checkpoint is seen, the screenshot is stored as the baseline and the check reports pass. On subsequent runs, the current screenshot is diffed against the baseline: identical → pass, above the similarity threshold → pass, below the threshold → fail.

#Full-page or viewport

By default visualCheckpoint captures the full page (including content scrolled out of view). Pass false as the second argument to capture only the current viewport.

Copied!
1// Full-page (default): great for landing pages and long tunnels.
2$this->visualCheckpoint('cart');
3 
4// Viewport-only: great for a header, a toolbar, a modal.
5$this->visualCheckpoint('order-shipping-modal', false);

#Frame a viewport capture on an anchor

Viewport-only captures show whatever happens to be at the top of the page — often a header, a promo banner or a cookie strip, not the piece you actually want to compare. CommonPage::scrollToAnchor($selector, $settleMs = 400) scrolls the matching element to the top of the viewport before the capture, so the frame lands on the business content.

Copied!
1$this->frontOfficePage->waitFor('.login-form'); // guarantee presence
2$this->frontOfficePage->scrollToAnchor('.login-form');
3$this->frontOfficePage->visualCheckpoint('login', null, 0.98, false);

Best-effort by design: if the selector is absent, the call is a no-op — the library doesn't know your project's DOM, so it's up to the suite to guarantee the anchor is present (via waitFor or similar) before calling. The optional $settleMs gives the browser a moment for a smooth-scroll animation to finish before the screenshot is taken.

#Baselines

Baselines are stored in a persistent folder (outside the ephemeral screens/ folder), so they survive between runs. Commit the baseline files alongside the suites that produce them so CI can compare against them.

To refresh a baseline after an intentional visual change, delete the file(s) for the affected checkpoint(s) and re-run the suite: the next run stores the new screenshot as the baseline.

#Report

Ask the CLI to write an HTML report at the end of the run:

Copied!
1bin/prestaflow run tests --visual-report

Or pick a custom path:

Copied!
1bin/prestaflow run tests --visual-report=reports/visual/2026-07-06.html

A visual-results.json side-file is written next to the HTML report. The report shows the reference, the actual capture and the diff side by side for each failing checkpoint.

#Time zone of the stamp

The report includes a "generated at" timestamp. By default it is in UTC; set PRESTAFLOW_TZ (or pass --visual-report-tz=…) to render the human timestamp in your local zone (the ISO attribute keeps its offset).

Copied!
1PRESTAFLOW_TZ=Europe/Paris bin/prestaflow run tests --visual-report