ScreenshotNeo

BlogHow-to

How to Fix Playwright Screenshot Tests That Capture a Blank Page

Diagnose blank Playwright screenshots by checking navigation, HTTP status, app readiness, and capture behavior—with runnable TypeScript examples and fixes.

By the ScreenshotNeo team4 October 20266 min read

A blank Playwright screenshot usually means the browser captured before the application showed its meaningful content, navigated somewhere unexpected, or received an error page. Start by checking the final URL and navigation response, then wait for a visible, app-specific readiness signal before capturing. page.goto() waits for the load event by default, but that does not guarantee a modern application has finished fetching data or rendering its UI. Playwright recommends web assertions for readiness and discourages using networkidle as a test strategy.

1. Check the navigation target and response

Inspect the URL after navigation and the response status. A response with status 404 or 500 does not necessarily make page.goto() throw, so a test can continue on an error page and capture it.

const response = await page.goto('/target');
console.log({
  url: page.url(),
  status: response?.status(),
  title: await page.title(),
});

If the URL or status is unexpected, check the configured base URL, route, redirects, authentication state, and the application’s own error screen. Confirm that the test is using the intended browser page and not a newly opened or replaced page.

2. Wait for the application to be ready

Use an assertion tied to content that means the page is usable: a heading, a loaded-state marker, or the data the test needs. A locator assertion retries while waiting for the condition. Replace the example heading with a signal that is meaningful in your application.

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

test('captures the rendered target page', async ({ page }) => {
  const response = await page.goto('/target');

  console.log({
    url: page.url(),
    status: response?.status(),
    title: await page.title(),
  });

  // Use a locator that proves this application is ready for the screenshot.
  await expect(
    page.getByRole('heading', { name: 'Expected page' })
  ).toBeVisible();

  await expect(page).toHaveScreenshot();
});

A fixed sleep can make a test slower and still flaky when rendering time varies. Playwright also discourages waiting for networkidle as a readiness condition for tests; pages can keep network connections open, while a quiet network does not prove that the required UI is visible.

3. Capture evidence before changing the baseline

When a test fails, save a diagnostic screenshot and inspect it alongside the URL, title, expected locator, browser console errors, and failed network requests. Playwright Test reporters can show an image attached to a named test step.

await test.step('inspect rendered page', async step => {
  await step.attach('page screenshot', {
    body: await page.screenshot(),
    contentType: 'image/png',
  });
});

You can also save a file while debugging locally:

await page.screenshot({ path: 'debug-page.png', fullPage: true });

Use fullPage: true when the content might be below the viewport. It will not fix a page whose content has not loaded. Check the screenshot itself before updating snapshots: a new baseline records the current rendering, including an accidental blank page.

4. Use Playwright’s visual assertion correctly

toHaveScreenshot() is a Playwright Test assertion. It waits for two consecutive screenshots to match before comparing the result with the expected snapshot. Use it after the application-specific readiness assertion so that the test first establishes that the intended content exists.

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

test('target page matches its visual baseline', async ({ page }) => {
  await page.goto('/target');
  await expect(page.getByRole('heading', { name: 'Expected page' })).toBeVisible();
  await expect(page).toHaveScreenshot('target-page.png');
});

If a visual change is intentional and the page is correctly rendered, update the baseline with your project’s snapshot update workflow, such as --update-snapshots. Do not use baseline updates to hide a navigation or readiness failure.

5. Separate blank-page bugs from visual instability

If expected content is present but screenshots differ between runs, investigate rendering consistency rather than adding more waits. Playwright notes that host operating system, browser version, settings, hardware, power source, and headless mode can affect screenshots. Generate and compare baselines in a consistent environment.

Screenshot assertion options can reduce visual noise. Playwright Test disables animations by default for screenshot assertions, and a stylePath stylesheet can hide volatile elements. These options help with changing timestamps, animations, or other dynamic regions; they cannot make missing application content appear.

await expect(page).toHaveScreenshot('target-page.png', {
  stylePath: './tests/screenshot.css',
});
/* tests/screenshot.css */
.live-clock,
.rotating-promo {
  visibility: hidden !important;
}

Use selectors that match only genuinely volatile content. Hiding broad page regions can conceal real regressions.

Common causes and fixes

Symptom Likely cause Fix
Screenshot is empty, but the test passes navigation The app renders after the browser’s load event. Wait for a visible, application-specific locator before capturing.
Screenshot shows an error page The target returned an HTTP error, redirected unexpectedly, or rendered an app-level error. Log page.url() and response?.status(); inspect redirects and the app’s error state.
Screenshot is blank only in CI Environment differences or missing configuration may change rendering or navigation. Compare browser version, OS, headless mode, settings, and required test state with the baseline environment.
Screenshot sometimes contains content and sometimes does not The test has no reliable readiness condition, or required data loads inconsistently. Assert on the content needed for the capture and inspect failed requests and console errors.
Screenshot cuts off the expected content The content is below the viewport. Use fullPage: true for diagnostic captures or the appropriate page screenshot assertion option.
Visual assertion changes from run to run although content is present Dynamic content, animation, or environment differences cause visual noise. Keep the environment consistent and use animation handling or a focused stylePath stylesheet.
Updating snapshots does not fix the underlying failure The new baseline recorded the wrong or blank state. Verify navigation and readiness first; update snapshots only for an intentional visual change.

Performance and reliability notes

  • Wait for a specific condition. A locator that represents usable content avoids arbitrary delay and makes the test’s requirement explicit.
  • Capture diagnostic artifacts on failure. Screenshots, URL, status, and relevant logs help distinguish navigation problems from late rendering and visual mismatch.
  • Keep snapshot environments consistent. Differences in operating system, browser, settings, hardware, power source, and headless mode can affect output.
  • Do not hide product behavior to stabilize a test. Restrict screenshot styles to volatile details that are irrelevant to the assertion.

Or skip the browser setup

If you need a website screenshot outside a Playwright test, ScreenshotNeo provides a screenshot API and MCP server. See the API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
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)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card required.

FAQ

Does page.goto() guarantee that my app is ready?

No. It waits for the configured navigation lifecycle event, which defaults to load; application data and UI can render afterward.

Should I use networkidle before taking the screenshot?

Playwright discourages networkidle as a test readiness strategy. Prefer an assertion for the visible state your test needs.

Why did a 404 not fail at page.goto()?

An HTTP error response does not necessarily throw during navigation. Inspect the returned response status and final URL.

When should I update a screenshot baseline?

After confirming the page rendered correctly and the visual change is intentional. A baseline update does not repair missing content or bad navigation.

Sources