How to Run Visual Regression Tests for a Laravel Website with Screenshots
Build stable screenshot checks for Laravel pages with Dusk or Playwright, compare reviewed baselines, and troubleshoot noisy diffs in CI.
Visual regression testing for a Laravel website has two parts: capture a page in a controlled browser environment, then compare the result with a reviewed baseline. Laravel Dusk can capture named, responsive, and element screenshots; it does not by itself provide a built-in baseline comparison assertion. For new projects, Laravel’s current documentation recommends considering Pest 4 for browser testing. Teams already using Dusk can keep their PHP workflow and add a compatible comparison tool or hosted review integration.
This guide shows Dusk screenshot capture, explains how to choose a comparison path, and covers Playwright Test for teams that prefer JavaScript screenshot assertions. Keep functional and accessibility checks alongside visual checks: a screenshot cannot tell you whether a control works or a page is accessible.
1. Choose a browser test foundation
Choose the runner based on the browser testing stack your team can maintain:
- New Laravel project: review the current Laravel recommendation for Pest browser testing and use the documentation matching your Laravel version.
- Existing Dusk suite: keep browser actions in PHP and capture screenshots through Dusk, then choose a compatible local comparison package or hosted integration.
- Existing Playwright team: Playwright Test can run browser tests and compare screenshots with stored expected snapshots. It is a JavaScript test stack, not a Laravel-specific PHP API.
These are alternative testing routes rather than one required stack. Compare language fit, where baselines live, how reviewers inspect diffs, CI support, environment control, and the maintenance required for packages or external services. The community Dusk/Percy integration is one hosted workflow; verify its current compatibility and service configuration before adopting it.
Sources: Laravel Dusk documentation, Playwright screenshot comparisons, Playwright snapshot assertion API, Dusk/Percy community integration, and community Dusk screenshot comparison package.
2. Make the page deterministic
A baseline is useful only when repeated captures have the same inputs. Choose a representative route or component, seed predictable records, and establish the same login and application state for each run. Avoid live data, randomized values, timestamps, rotating banners, and other changing content unless that content is specifically under test. Where appropriate, freeze or mask variable regions through the comparison tool.
- Pick a page and define the state it should show, including user role and seeded data.
- Choose fixed viewport dimensions for each scenario. Treat desktop and mobile as separate cases instead of inheriting a developer’s display size.
- Use the same browser version, operating-system image, fonts, device scale, and headless settings in local runs and CI where possible.
- Wait for relevant content, images, and fonts to finish loading. Settle animations and transient UI before capture. The precise wait mechanism depends on the page and runner.
- Capture and compare the result, then review any changed regions in context before approving a new baseline.
Browser screenshots can vary with operating system, browser and runtime settings, hardware, power conditions, headless mode, and viewport. Playwright documents these sources of variation. Investigate environment drift before loosening comparison settings.
3. Capture screenshots with Laravel Dusk
Dusk documents whole-page named screenshots, responsive screenshots, and screenshots of a selected element. Regular Dusk screenshots are stored under tests/Browser/screenshots. Use the exact methods supported by the Dusk version installed in your project.
<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class AccountSettingsVisualTest extends DuskTestCase
{
public function test_account_settings_screenshots(): void
{
$this->browse(function (Browser $browser) {
$browser->loginAs(User::factory()->create())
->visit('/account/settings')
->assertSee('Account settings')
->screenshot('account-settings-desktop');
});
}
}
The example assumes your project has a User model and the route is available to an authenticated user. Add the appropriate imports and deterministic setup for your application. The screenshot call captures an image; it does not establish or compare an approved baseline on its own.
Capture an element or responsive set
$browser->screenshotElement('#profile-card', 'profile-card');
$browser->responsiveScreenshots('account-settings');
Element screenshots reduce unrelated page noise when the component is the intended target. Whole-page screenshots are more useful for layout changes that span sections. Responsive screenshots produce a set for multiple viewports; keep those cases named and reviewed like other test snapshots.
4. Compare screenshots with a baseline
Capture and comparison are separate steps. Select one of these approaches after deciding how the team will store, review, and approve references:
| Approach | Good fit | Tradeoff |
|---|---|---|
| Playwright Test screenshot assertions | The team already uses JavaScript browser tests or accepts a JavaScript runner. | Adds or maintains a browser test stack alongside the PHP application. Rendering conditions must stay consistent. |
| Dusk plus local/community comparison | The team wants browser actions in PHP and reference management in its repository. | Verify package compatibility and maintenance for the installed Laravel and Dusk versions; the team owns baseline review. |
| Dusk plus Percy integration | The team wants a hosted visual review/build workflow. | Requires external service configuration and a token; the cited Dusk integration is community-maintained. |
Playwright Test example
In a Playwright project configured to reach the Laravel application, a test can compare a page against its stored expected screenshot:
import { test, expect } from '@playwright/test';
test('account settings visual baseline', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto('http://127.0.0.1:8000/account/settings');
await expect(page.getByRole('heading', { name: 'Account settings' })).toBeVisible();
await expect(page).toHaveScreenshot('account-settings-desktop.png', {
fullPage: true,
});
});
Configure the test project’s web server, authentication, and snapshot update/review workflow for your app. Playwright’s toHaveScreenshot() compares against an expected snapshot; its options and update process are documented in the visual comparisons guide and snapshot assertion API. Review generated diffs before updating expected images. Do not accept snapshot updates blindly.
5. Review diffs without hiding defects
A pixel difference is a signal for review, not proof of a defect. Check whether a changed area reflects an intentional design or content update, a real regression, dynamic content, or a changed capture environment. Compare the image in page context and confirm the relevant functional assertions still pass. Update a baseline only after deciding the visual change is intended.
- Keep visual assertions scoped to stable pages or components.
- Use separate desktop and mobile scenarios with explicit dimensions.
- Keep test data and browser inputs consistent across runs.
- Retain assertions for routes, text, authorization, and interaction behavior.
- Pair visual checks with accessibility testing; pixels alone do not establish accessibility.
6. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Large diffs on every run | Browser, OS, fonts, viewport, device scale, headless mode, or hardware differs. | Pin the browser/runtime environment and viewport in local and CI runs. Check environment drift before changing comparison thresholds. |
| Only timestamps, prices, or rotating content differ | Non-deterministic or live page data. | Seed fixed data, freeze time where appropriate, or mask the variable region using the chosen comparison tool. |
| Images or text sometimes appear incomplete | The capture starts before relevant assets or content finish rendering. | Wait for a page-specific ready condition and ensure fonts and images are loaded before taking the screenshot. |
| Dusk screenshot exists but no test fails on a visual change | Capture was added without a baseline comparison step. | Add a compatible comparison assertion or integration and define where approved references are stored. |
| Element screenshot is empty or wrong | The selector does not match the intended rendered element, or the element is not ready. | Assert the element is present and visible, verify the selector, and capture after the page reaches its stable state. |
| Snapshot update makes CI green but a defect remains | The changed image was accepted without review. | Inspect the diff and restore the prior reference if the change is unintended. Require review for baseline changes. |
| Community package fails with current dependencies | Its constraints or maintenance may not match the project’s Laravel/Dusk versions. | Check package compatibility, Composer constraints, release activity, and CI behavior before relying on it. |
7. Performance, reliability, and cost
Screenshot suites add browser navigation, rendering, image storage, and comparison work. Keep the suite focused on representative pages and components, and avoid duplicate captures that do not answer a distinct visual question. Element captures can limit unrelated page area; full-page screenshots cover broader layout at the cost of larger images and more content to review. Run stable, high-value cases in the main CI path and schedule broader coverage according to the team’s runtime budget.
Reliability depends on controlling the rendering environment and page state. Keep baseline images reviewable and versioned or available in the selected review workflow. A mechanically passing comparison can still preserve a regression if the reference was updated without inspection.
The research sources do not establish current package or hosted-service prices, quotas, or plan limits. Check the selected tool’s current terms and account requirements before budgeting; do not infer cost from the existence of a package or integration.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. It returns a screenshot or PDF from one GET request. A captured image can be useful as a review artifact, but a screenshot API call by itself does not replace a deterministic browser test or baseline comparison assertion.
Example cURL request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
See the ScreenshotNeo API documentation for request options. Cookie banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000, and every feature is available on every plan.
Sign up for 1,000 free screenshots a month, with no card required.
9. Frequently asked questions
Does Laravel Dusk compare screenshots automatically?
Dusk documents screenshot capture methods. Baseline comparison is a separate step that requires a comparison tool or integration.
Should I use Dusk or Playwright for a Laravel app?
Use the runner that fits the team’s existing test stack and maintenance capacity. Dusk keeps browser tests in PHP; Playwright Test offers screenshot assertions in a JavaScript test stack.
Can visual regression tests replace feature tests?
No. Visual checks detect appearance changes. Keep behavior, authorization, content, and accessibility checks in the test suite.
Should I update a baseline whenever a test reports a diff?
Only after reviewing the changed image and confirming the difference is intended. An unreviewed update can hide a regression.


