ScreenshotNeo

BlogHow-to

How to Fix Playwright Screenshot Timeout Errors

Diagnose whether a Playwright screenshot call, test, or visual assertion is timing out, then apply the fix to the correct timeout.

By the ScreenshotNeo team4 October 20267 min read

To fix a Playwright screenshot timeout, first identify which operation timed out. A direct page.screenshot() call, the enclosing Playwright Test, and expect(page).toHaveScreenshot() have different timeout behavior and configuration. Increase only the limit that owns the error; if the page is not ready, wait for a meaningful page condition instead of adding a fixed sleep.

1. Identify which timeout expired

Read the error and call log before changing configuration. The word “screenshot” can appear in errors from different layers.

What the failure points to What it means Where to look
page.screenshot() or the screenshot operation The direct capture call did not finish within its configured operation limit. The options passed to that screenshot call. The Page API documents a default screenshot timeout of 0. [Playwright Page API]
Test timeout of ... exceeded The whole test exceeded its budget. Setup, fixtures, hooks, navigation, assertions, and screenshot work can all contribute. The test’s timeout and any applicable project or global configuration. Playwright Test documents a 30,000 ms per-test default. [Playwright Test timeouts]
An expect or toHaveScreenshot() assertion timeout An assertion did not satisfy its condition in its assertion window. A screenshot assertion also seeks a stable visual result. Assertion timeout and expect.toHaveScreenshot configuration, rather than the direct screenshot call option. The documented expect timeout default is 5,000 ms. [Playwright Test timeouts] [Visual comparisons]

These defaults describe different scopes. A screenshot API timeout of zero does not mean a Playwright Test has no time limit, and raising the test timeout does not necessarily fix a screenshot assertion that cannot stabilize.

2. Fix a direct screenshot call timeout

Set the timeout option on the particular capture when the error identifies page.screenshot(). The value below is an example, not a universal recommendation; choose a limit based on observed capture time and the total time budget of the test.

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

test('capture the dashboard', async ({ page }) => {
  await page.goto('https://example.com/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await page.screenshot({ path: 'dashboard.png', timeout: 30_000 });
});

For JavaScript without TypeScript annotations, the same runnable test works in a .spec.js file. Run it with npx playwright test after installing and configuring Playwright Test for your project. The capture option is local to this screenshot operation; it does not enlarge the time available to all other test work.

3. Fix an enclosing Playwright Test timeout

If the error says the test timeout was exceeded, budget for the whole test, not just the final image write. A per-test override can be placed inside the test:

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

test('capture a slow dashboard', async ({ page }) => {
  test.setTimeout(60_000);

  await page.goto('https://example.com/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await page.screenshot({ path: 'dashboard.png', timeout: 30_000 });
});

Alternatively, configure a project or suite timeout in playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: 60_000,
  expect: { timeout: 5_000 },
});

Keep the distinction clear: timeout sets the test budget in this configuration; expect.timeout controls regular web-first assertions. Screenshot comparison has its own expect.toHaveScreenshot settings. Check the current official timeout guide for the scopes that apply to your installed Playwright version. [Playwright Test timeouts] [TestConfig API]

4. Wait for the page condition that matters

A screenshot can time out or capture an incomplete state because navigation alone did not mean the page was ready for your use case. Wait for a visible, meaningful signal such as the page heading, a loaded result, or an application-specific ready marker:

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

test('capture loaded results', async ({ page }) => {
  await page.goto('https://example.com/reports');
  await expect(page.getByRole('heading', { name: 'Reports' })).toBeVisible();
  await expect(page.getByTestId('report-results')).toBeVisible();
  await page.screenshot({ path: 'reports.png', timeout: 30_000 });
});

Use signals that actually represent readiness for the screenshot. If results arrive from a known request, wait for that response; if the relevant state is in the UI, assert that state. Playwright cautions that fixed page.waitForTimeout() sleeps are unreliable in production tests; condition-based waits are less dependent on machine speed. [Playwright auto-waiting]

5. Treat toHaveScreenshot() as a visual assertion

page.screenshot() writes an image. expect(page).toHaveScreenshot() compares a captured image with a visual baseline. The latter can take repeated screenshots and retry until consecutive captures match, so animation, timestamps, rotating content, or other dynamic regions can prevent a stable comparison. [Visual comparisons]

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

test('dashboard matches its visual baseline', async ({ page }) => {
  await page.goto('https://example.com/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await expect(page).toHaveScreenshot('dashboard.png', {
    animations: 'disabled',
    timeout: 10_000,
  });
});

Use documented screenshot assertion options to handle known animation or dynamic content. A larger timeout gives the assertion more time, but it does not make a changing page deterministic. If the error is from a regular expect(locator) assertion earlier in the test, adjust that assertion’s timeout or fix its readiness condition instead.

6. Troubleshooting common failures

Symptom Likely cause Fix
Screenshot operation times out despite a larger test timeout The direct screenshot call has its own operation options or the page is stuck in a state that prevents capture from completing. Inspect the call log and set an appropriate page.screenshot({ timeout }) value. Confirm the page reached the expected state and check for page errors or a stalled browser.
Test timeout occurs after navigation, with little time left for capture Navigation, setup, fixture work, or other actions consumed most of the enclosing test budget. Measure which steps take time, remove unnecessary work, or increase the per-test/configured test timeout. Keep a separate screenshot timeout if the capture itself needs one.
toHaveScreenshot() keeps retrying or fails to stabilize Animation or dynamic pixels differ between consecutive captures. Disable or handle animations using documented screenshot options; mask or otherwise control dynamic regions where appropriate. Increase the assertion timeout only if stability is expected to take longer.
Screenshot captures a blank or partially rendered page The test captured before the application’s relevant content was ready, or an expected request failed. Wait for a meaningful locator or response; inspect failed network requests and application errors. Avoid arbitrary sleep durations.
Failure appears only in CI Different load, browser, fonts, viewport, or resource timing can expose an overly tight limit or readiness assumption. Check CI logs and call logs, confirm the same browser and dependencies are installed, wait on application state, and budget the full test. Do not blindly multiply every timeout.

7. Performance, reliability, and cost

Increasing timeouts does not make screenshot capture faster; it changes how long the test is willing to wait before failing. Condition-based waits avoid spending a fixed delay on every run and make readiness explicit. Keep test, assertion, and screenshot-call budgets aligned so one operation cannot consume more time than the enclosing test has left.

For reliability, make the captured state deterministic: use stable test data, wait for the intended UI or network condition, and control animation and volatile content in visual comparisons. In CI, inspect the first failing operation and its call log before changing global defaults. A larger global timeout can hide slow setup or a page that never becomes ready.

Playwright itself does not charge per screenshot in this workflow; execution cost comes from the machines and CI time used to run the browser and tests. This guide has no benchmark or universal timeout recommendation: measure your own runs and select limits that fit your environment.

Or skip the browser setup

If you need a screenshot of a URL rather than a Playwright test, ScreenshotNeo provides a one-request screenshot API. See the API documentation for its 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

ScreenshotNeo accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers say the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots monthly without a card; paid plans start at $5 for 3,000 shots. Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Does page.screenshot() inherit the Playwright Test timeout?

The direct screenshot API documents its own timeout option, with a default of zero. The test runner separately limits the enclosing test, so either scope can be the one reported as failed. [Page API] [Test timeout guide]

Should I always set the screenshot timeout to 30 seconds?

No. That is an example value. Choose a call limit from your capture behavior and make sure the enclosing test has enough remaining time.

Why does a screenshot assertion take longer than saving a screenshot?

A visual assertion must compare against a baseline and can retry captures until they are stable. A direct screenshot only captures and writes the image. [Visual comparisons]

Can I fix a timeout by adding waitForTimeout()?

A fixed sleep can mask the actual readiness condition and make runs flaky. Prefer waiting for the locator, response, or application state that the screenshot depends on. [Playwright auto-waiting]