ScreenshotNeo

BlogHow-to

How to Capture a Website Screenshot with a Delay Using Playwright

Learn when to wait before a Playwright screenshot, with runnable JavaScript, Python, and cURL examples, reliable readiness signals, and troubleshooting tips.

By the ScreenshotNeo team4 October 20268 min read

To capture a Playwright screenshot after a fixed delay, await page.waitForTimeout(milliseconds) immediately before page.screenshot(). That is useful for debugging, but Playwright advises against timer-based waits in production tests because they can be flaky. For a reliable capture, wait for the specific content or readiness signal the screenshot depends on, then take the screenshot.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();

try {
  await page.goto('https://example.com');
  await page.waitForTimeout(2000); // Fixed 2-second pause; best reserved for debugging.
  await page.screenshot({ path: 'screenshot.png' });
} finally {
  await browser.close();
}

Choose the right wait for the screenshot

A fixed pause waits a set amount of time regardless of what the page is doing. A condition-based wait completes when an observable event occurs. Prefer the condition that corresponds to the pixels you need.

Approach Use it when Trade-off
page.waitForTimeout(ms) Debugging a timing issue or temporarily reproducing a delay Can waste time when the page is ready early, or still be too short when it is slow
Locator wait The screenshot depends on a particular element appearing or becoming visible Requires a selector and expected state that represent actual readiness
Load-state wait You specifically need a navigation load boundary A load event does not guarantee that asynchronous app content is rendered
Network or app-specific signal The page exposes a meaningful event that marks the content ready Requires knowing which request or app signal matters

Use a fixed delay (debugging)

Place the wait after navigation and directly before the screenshot. The argument is milliseconds, so 2000 means two seconds. The screenshot API has no documented delay option; waiting is a separate step.

await page.goto('https://example.com');
await page.waitForTimeout(2000);
await page.screenshot({ path: 'screenshot.png' });

Playwright’s Page API says waitForTimeout should only be used for debugging and warns that tests waiting on timers in production are inherently flaky. A fixed wait can be a useful diagnostic: if adding it makes a screenshot look right, identify what the page was waiting for and replace the timer with that signal.

If the capture depends on a heading, chart, or other element becoming visible, wait for that locator. Replace the example selector and name with values from the target page.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();

try {
  await page.goto('https://example.com');
  await page.getByRole('heading', { name: 'Ready' }).waitFor({ state: 'visible' });
  await page.screenshot({ path: 'screenshot.png' });
} finally {
  await browser.close();
}

You can also use a web-first assertion when you want the check to fail with a useful assertion error:

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

const browser = await chromium.launch();
const page = await browser.newPage();

try {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading', { name: 'Ready' })).toBeVisible();
  await page.screenshot({ path: 'screenshot.png' });
} finally {
  await browser.close();
}

Use a signal tied to the actual screenshot requirement. If a chart is the important region, wait for the chart or its data-ready state rather than an unrelated heading. Playwright actions and locator assertions often wait automatically for their own conditions.

Load states and asynchronous pages

page.goto() accepts a waitUntil option, and page.waitForLoadState() can wait for a navigation state such as load, domcontentloaded, or networkidle. Use these when that boundary is what you need. A page can finish loading while client-rendered content or data fetched later is still missing, so a load state is not a universal signal that the screenshot is ready.

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.getByTestId('report-chart').waitFor({ state: 'visible' });
await page.screenshot({ path: 'report.png' });

If you do use an explicit load-state wait, the navigation must have been committed; if the requested state has already been reached, the wait resolves immediately. Avoid waiting for networkidle simply as a substitute for understanding the page: ongoing analytics, polling, or other background requests may keep a page busy even when the target content is ready.

Capture options that affect the result

page.screenshot() returns an image buffer; pass path to save it. By default it captures the viewport. Set fullPage: true to capture the full scrollable page.

await page.screenshot({ path: 'full-page.png', fullPage: true });
Option Effect
path Writes the screenshot to a file; without it, use the returned buffer.
fullPage Captures the full scrollable page when true; defaults to false.
type Selects png, jpeg, or webp.
scale Chooses css or device scale.
style Applies CSS during capture.
timeout Sets the screenshot operation timeout.
animations Controls animation handling; see below.

For example, a consistent full-page WebP capture with animations disabled:

await page.screenshot({
  path: 'page.webp',
  fullPage: true,
  type: 'webp',
  animations: 'disabled',
  timeout: 30000
});

By default, animations are allowed. With animations: 'disabled', finite animations are fast-forwarded and infinite animations are canceled to their initial state for capture, then played again afterward. This can help when animation changes make captures inconsistent. These details are documented in the Playwright screenshot options.

Runnable project setup

For a small standalone JavaScript script, install Playwright and its browser binaries, then save the example as an ES module:

npm init -y
npm install playwright
npx playwright install chromium
// screenshot.mjs
import { chromium } from 'playwright';

const target = process.argv[2] ?? 'https://example.com';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

try {
  await page.goto(target, { waitUntil: 'domcontentloaded' });
  // Replace this debug pause with a locator or application signal for production.
  await page.waitForTimeout(2000);
  await page.screenshot({ path: 'screenshot.png', fullPage: true });
  console.log('Saved screenshot.png');
} finally {
  await browser.close();
}
node screenshot.mjs https://example.com

Python and command-line options

Python’s Playwright API follows the same pattern: navigate, await a condition or delay, then capture. Install the package and browser first:

python -m pip install playwright
python -m playwright install chromium
# screenshot.py
import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": 1440, "height": 900})
        try:
            await page.goto("https://example.com", wait_until="domcontentloaded")
            # Debug-only fixed delay; replace with a meaningful readiness signal in production.
            await page.wait_for_timeout(2000)
            await page.screenshot(path="screenshot.png", full_page=True)
        finally:
            await browser.close()

asyncio.run(main())

To wait for content instead, replace the timer with:

await page.get_by_role("heading", name="Ready").wait_for(state="visible")

Playwright also provides a CLI screenshot command for quick captures. It does not provide a screenshot delay flag in the documented screenshot options; for a delayed or condition-based capture, use a script so you can await the signal before capture.

Or skip the browser setup

Playwright is useful when you need browser automation and control over the page. For a one-call screenshot API, ScreenshotNeo accepts a URL and returns an image or PDF. Its API supports a configurable wait, full-page capture, selectors, custom viewport, and other capture settings. See the ScreenshotNeo API documentation.

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 removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free for 1,000 screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause Fix
Screenshot misses content that appears later The script captured after navigation but before asynchronous rendering completed Wait for the relevant locator, app-ready signal, or specific network event before capture.
Screenshot sometimes works and sometimes does not A fixed delay is shorter than some page loads and longer than others Replace the timer with a condition tied to the required content; retain a reasonable timeout to detect a genuine failure.
Locator wait times out The selector or accessible name does not match, the element never appears, or it is not visible Inspect the page and verify the locator and expected state. Wait for the state the screenshot actually needs.
waitForLoadState returns but the page looks incomplete The load boundary occurred before client-side rendering or later data fetching finished Wait for an app-specific element or readiness signal after the load state.
Wait hangs or times out on network idle Background requests, polling, or analytics keep network activity going Wait for the relevant content or request instead of global network quiet.
Screenshot file is missing The path is relative to a different working directory, or the script exited before capture completed Use an absolute path when needed and await page.screenshot() before closing the browser.
Capture differs because of motion Animations or transitions changed the page during capture Use animations: 'disabled' when a static state is appropriate.
Browser launch fails after package installation The Playwright browser binaries have not been installed in that environment Run npx playwright install chromium for Node.js or python -m playwright install chromium for Python.

Performance, reliability, and cost

  • Performance: A fixed delay adds its full duration even when the page becomes ready sooner. A condition-based wait can proceed as soon as its condition is met. Full-page screenshots may take longer and produce larger files than viewport captures.
  • Reliability: A timer is only a guess about page readiness. A page-specific locator or app signal is more directly connected to the content being captured. Use timeouts to make stalled navigation, waits, and captures fail visibly rather than waiting forever.
  • Resource use: Close the browser in a finally block so errors do not leave browser processes running. Reuse a browser across multiple captures in a controlled batch when appropriate, while isolating page state between targets.
  • Cost: Playwright is an open-source browser automation library, but running captures still consumes the compute and storage of the environment where the browser runs. A hosted screenshot API trades local browser operations for service usage; compare its plan limits and response semantics to your workload. ScreenshotNeo’s free and paid prices are stated above.

FAQ

Does page.screenshot() have a delay option?

No delay option is listed in the screenshot API. Await a separate timer or readiness condition before calling it.

Can I wait one second before every screenshot?

You can use await page.waitForTimeout(1000), especially while debugging. For production tests, prefer a signal that confirms the required content is ready.

Does fullPage: true wait for lazy-loaded content?

It requests a full scrollable-page capture; it is not itself a readiness condition for every item the page may load asynchronously. Wait for the content your capture requires.

Which image format should I choose?

Use PNG when lossless output is useful, or JPEG and WebP when those formats fit your downstream workflow. The screenshot API supports these types.

References