← Back to list

Stabilizing visual tests with API mocking in Playwright

1. Visual tests next to functional tests

D Desutter · 2026-03-07 07:20 · 2 claps · 3.2 min read
#software-testing #test-automation #playwright-automation #automated-visual-testing #api-mocking
Open on Medium ↗

Stabilizing visual tests with API mocking in Playwright

1. Visual tests next to functional tests

Problem

Functional tests verify behavior, but they often miss visual regressions. Examples of issues functional tests won’t detect:

  • Broken layouts after CSS change
  • Missing icons or images
  • Incorrect fonts or styling
  • Overlapping components

A test may pass because the button is clickable, even if the UI looks broken. Example:

await page.click('#checkout');
await expect(page.locator('#confirmation')).toBeVisible();

The test passes even if the page layout is visually broken.

Solution

Add visual snapshot tests next to your functional tests using Playwright. Playwright can capture a screenshot and compare it with a baseline snapshot.

import { test, expect } from '@playwright/test';

test('checkout page looks correct', async ({ page }) => {
  await page.goto('/checkout');
  // Functional validation
  await expect(page.locator('h1')).toHaveText('Checkout');
  // Visual validation
  await expect(page).toHaveScreenshot('checkout-page.png');
});

Playwright stores a baseline screenshot and compares future runs against it. If the UI changes, the test fails and shows a diff image comparing the baseline with the actual result.

Benefits and Examples

Benefits:

  • Detect UI regressions automatically
  • Catch CSS and layout issues
  • Validate complete page rendering
  • Reduce manual visual QA

Think of it as:

Functional tests check what the UI does Visual tests check what the UI looks like

2. Stabilizing visual tests with network mocking

Problem

Visual tests can become unstable if they rely on dynamic data from APIs. Examples:

  • Random usernames
  • Changing timestamps
  • Rotating product lists
  • Real-time prices

This causes screenshot differences even when the UI is correct.Example:

await page.goto('/dashboard');
await expect(page).toHaveScreenshot();

If the dashboard contains dynamic data, the screenshot will change every run.

Solution

Use Playwright network interception to mock the API responses. This ensures the UI always renders predictable data.

test('dashboard visual test with stable data', async ({ page }) => {
await page.route('**/api/dashboard', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        user: "Test User",
        notifications: 3,
        lastLogin: "2024-01-01"
      })
    });
  });
  await page.goto('/dashboard');
  await expect(page).toHaveScreenshot('dashboard.png');
});

Now the UI renders consistent test data, ensuring stable visual snapshots.

Benefits and Examples

Benefits:

  • Eliminates flaky visual tests
  • Ensures deterministic UI rendering
  • Allows testing edge cases
  • Removes dependency on backend availability

Example use cases:

  • Show an empty state
  • Display specific error messages
  • Force edge-case UI states
  • Freeze timestamps and values

Example mocked data scenario:

{
  "orders": [
    {
      "id": 1,
      "status": "Delivered",
      "price": 99.99
    }
    {
      "id": 2,
      "status": "Returned",
      "price": 49.99
  ]
}

Your UI will always render the same orders, producing stable screenshots.

3. Hybrid mocking (combining real and mocked data)

Problem

Full mocking is stable, but sometimes you still want real backend responses.

Reasons:

  • Validate API contracts
  • Use realistic data structures
  • Avoid duplicating backend logic in tests

However, real responses still contain unstable fields like timestamps or IDs.

Solution

Use hybrid mocking:

  1. Fetch the real API response
  2. Modify unstable fields
  3. Return the modified response to the browser
test('hybrid mocked visual test', async ({ page, request }) => {
await page.route('**/api/profile', async route => {

    const response = await request.fetch(route.request());
    const data = await response.json();
    // Override unstable fields
    data.lastLogin = "2024-01-01";
    data.notifications = 5;
    await route.fulfill({
      response,
      body: JSON.stringify(data)
    });
  });
  await page.goto('/profile');
  await expect(page).toHaveScreenshot('profile.png');
});

Benefits and Examples

Benefits:

  • Keep realistic API responses
  • Remove unstable fields
  • Maintain stable visual snapshots
  • Reduce maintenance of large mock datasets

Example: Real API response:

{
  "name": "Alice",
  "lastLogin": "2026-03-06T12:43:22Z",
  "notifications": 7
}

Hybrid mocked response:

{
  "name": "Alice",
  "lastLogin": "2024-01-01",
  "notifications": 5
}

The UI remains realistic while visual tests stay stable.

Recommended framework structure

Keep visual and functional tests separated. This ensures:

  • clear responsibility
  • faster debugging
  • independent pipelines
tests
 ├── functional
 │    ├── login.spec.ts
 │    └── checkout.spec.ts
 │
 ├── visual
 │    ├── login.visual.spec.ts
 │    └── checkout.visual.spec.ts
 │
 └── mocks
      └── apiMocks.ts

Key Takeaway

Reliable visual testing in Playwright comes from three layers:

  1. Functional tests → validate behavior
  2. Visual snapshots → detect UI regressions
  3. Network mocking → guarantee stable rendering

Together they provide high-confidence UI testing with minimal flakiness.

Checkout my other article about snapshot data management:

[embed]Keeping Playwright visual tests clean and reliable with a separate snapshot repo Visual testing in Playwright is amazing for catching UI regressions — until the repo gets cluttered with hundreds of…medium.com

Useful links: Visual Testing in Playwright API mocking in Playwright


메타데이터
post_id
1bb2aac0aba4
slug
stabilizing-visual-tests-with-api-mocking-in-playwright-1bb2aac0aba4
url
https://medium.com/@d.desutter91/stabilizing-visual-tests-with-api-mocking-in-playwright-1bb2aac0aba4
canonical_url
https://medium.com/@d.desutter91/stabilizing-visual-tests-with-api-mocking-in-playwright-1bb2aac0aba4
author_url
https://medium.com/@d.desutter91
status
ok
fetched_at
2026-07-24 01:22:17