How to Capture a Full-Page Screenshot After a Page Finishes Loading in Playwright
Wait for the right page state, then use Playwright’s fullPage screenshot option. Learn how to handle asynchronous content, troubleshoot captures, and use ScreenshotNeo without browser setup.
To capture the full scrollable page after navigation finishes loading, wait for the page’s load event and set fullPage: true:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
try {
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
} finally {
await browser.close();
}
})();
page.goto() already defaults to waitUntil: 'load'; spelling it out makes the readiness condition clear. The fullPage option captures the full scrollable page rather than just the visible viewport. See the Playwright Page API and screenshot guide.
1. Choose the readiness condition your screenshot needs
“Finished loading” can mean different things. The right wait condition depends on whether the screenshot needs only the document resources or content rendered later by the application.
| Condition | What it means | Use it when |
|---|---|---|
domcontentloaded |
The HTML was parsed and the DOMContentLoaded event fired. |
The workflow needs the parsed document and does not depend on later resources. |
load |
The page’s load event fired. This is page.goto()’s default. |
You want a general baseline after document resources load. |
| Locator or web assertion | A chosen UI condition is satisfied. | Important content appears asynchronously or a particular component must be present. |
networkidle |
There were no network connections for at least 500 ms. | Use cautiously. Playwright discourages relying on this for tests; background connections can prevent the condition from occurring. |
commit |
The response was received and document loading started. | You need to know navigation began, not that the page finished loading. |
A page can continue rendering asynchronous content after its load event. When that content matters, wait for a locator that represents the content you need. Choose a real signal from the target application rather than assuming a generic event means the page is visually complete.
2. Capture a page that loads content asynchronously
For example, if the main content appears after a request, wait for the main region to become visible before taking the screenshot:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
const url = 'https://example.com';
try {
await page.goto(url, { waitUntil: 'load' });
await page.getByRole('main').waitFor({ state: 'visible' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
} finally {
await browser.close();
}
})();
Replace getByRole('main') with a locator that indicates the required content is ready on your site. If the page has a specific success state, wait for that state. A visible container may still be empty, so use a more precise locator when necessary.
For a navigation that already happened
If another action triggered navigation, wait for the relevant load state before capturing. waitForLoadState() requires a committed navigation; if the requested state has already occurred, it resolves immediately.
await page.waitForLoadState('load');
await page.screenshot({ path: 'full-page.png', fullPage: true });
3. Full-page screenshot options and practical details
fullPage: true: Capture the entire scrollable page. Without it, the screenshot is limited to the viewport.path: Save the screenshot to a file, as in the examples. Without a path,page.screenshot()returns a buffer you can store or process.- Readiness: The screenshot call does not mean the page’s application-specific asynchronous work has completed. Wait for the condition that matters before capturing.
Keep the viewport and browser environment consistent when comparing screenshots. Full-page capture can produce a much taller image than a viewport capture, so large pages may require more memory and storage. Decide whether your workflow needs a visual artifact, an in-memory buffer, or both.
4. Avoid fixed sleeps and understand screenshot stability
A fixed delay such as waitForTimeout(3000) guesses how long the page will take. It can waste time on fast runs and still capture too early on slow ones. Playwright discourages timer-based waits for tests; use a locator or web assertion tied to the page state instead.
Ordinary page.screenshot() does not promise to wait for two identical renders. Playwright Test’s expect(page).toHaveScreenshot() has separate visual-stability behavior: it takes screenshots until two consecutive screenshots match, then compares the last one with the expectation. If you are doing visual comparisons, keep the browser version, operating system, settings, hardware, power conditions, and headless mode consistent because rendering can vary across environments. See Playwright visual comparisons.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot is only the visible viewport. | The screenshot call omitted the full-page option. | Set fullPage: true. |
| Content is missing even though navigation completed. | The application rendered that content asynchronously after the chosen load event. | Wait for a locator or application-specific readiness condition that represents the missing content. |
networkidle never arrives. |
The page may keep background network connections open, or new requests may continue. | Use a meaningful locator or web assertion rather than depending on network quiet. |
waitForLoadState() fails or waits unexpectedly. |
It is being used without a committed navigation, or the chosen state does not match the workflow. | For a new navigation, pass the desired waitUntil to goto(). For an existing navigation, call waitForLoadState() after the action that triggers it; remember it resolves immediately if that state already occurred. |
| Visual snapshots differ between runs or machines. | Rendering may vary with browser, host environment, settings, hardware, power source, or headless mode. | Run comparisons in a consistent environment and use Playwright Test’s screenshot assertion when you need its stability and comparison behavior. |
| A fixed timeout works sometimes but fails intermittently. | Load duration varies, so the guessed delay is sometimes too short or unnecessarily long. | Wait for the required UI state instead of sleeping for a fixed duration. |
6. Performance, reliability, and cost
The readiness condition affects how long the workflow waits. domcontentloaded can be enough when later resources do not matter; load waits for the page’s load event; and a locator waits for the particular UI state required. There is no universally best signal. Choose the condition that matches the content you must capture.
Full-page images can be large because they include content beyond the viewport. Keep only the output format and dimensions your downstream workflow needs, and avoid recapturing the same page unnecessarily. Reliability improves when readiness is tied to an observable page condition instead of a guessed delay. Playwright itself has no per-screenshot API fee in this procedure, but running browser automation has infrastructure and maintenance costs: you need a compatible browser setup and resources to launch and operate it. The supplied sources do not provide benchmark or cost figures.
7. Or skip the browser setup
For a one-request screenshot, ScreenshotNeo is a website screenshot API and MCP server. Its API returns an image or PDF from a URL. 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 the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use screenshot tools, including take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a 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.
8. FAQ
Does fullPage: true wait for all page content?
It controls the capture area, not application readiness. Wait for the relevant page state before calling the screenshot method.
Should I always use networkidle?
No. Playwright discourages relying on it for tests. Pages with background traffic may not become idle, and a specific locator or assertion is usually a clearer readiness signal.
Does page.screenshot() wait for two matching frames?
No such stability guarantee is described for ordinary screenshots. Playwright Test’s toHaveScreenshot() provides screenshot comparison behavior.
Can I wait for load after it already fired?
Yes. waitForLoadState('load') resolves immediately when that state has already been reached, provided the navigation was committed.


