Recommended Free Tools
Await the screenshot call, and separately wait for the page state you need to capture. In Playwright, page.screenshot() is asynchronous: use await so your code does not try to save or use the image before capture finishes. That alone does not mean the page has finished rendering the content you care about. Wait for a meaningful UI condition or URL first, then take the screenshot.
What asynchronous screenshot capture means
A screenshot is the result of browser work: the page must be rendered and the browser must produce image data. In both Playwright and Puppeteer, the screenshot API completes asynchronously. Await its Promise before saving, returning, uploading, or otherwise consuming the image.
There are two separate waits to consider:
- Page readiness: wait until the page is in the state the image should show, such as a dashboard heading becoming visible or a navigation reaching the expected URL.
- Capture completion: await the screenshot operation itself before using its result.
A completed screenshot call means the image data is ready; it does not certify that your application loaded the right data or that a particular component appeared. Choose a readiness condition that matches the screenshot’s purpose.
Take an asynchronous screenshot with Playwright
Here is a minimal Node.js example using Playwright. Replace the URL and heading with the page and visible content that matter to your case:
#1 Best Overall
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
await page.screenshot({ path: 'dashboard.png', fullPage: true });
} finally {
await browser.close();
}
})();
The order is intentional: navigate, wait for a visible page condition, then await the screenshot. The path option writes the image to a file; fullPage: true asks for the full page rather than just the current viewport. Without a path, the screenshot call returns image data for your code to handle.
Use a readiness condition that represents the image
The sample waits for a heading. In a real flow, wait for a selector, text, or other user-visible condition that demonstrates the content is ready. For example, a heading may appear before asynchronous dashboard data; if the screenshot must include a populated chart, wait for the chart or its loaded state instead. A generic navigation milestone is useful only when reaching that milestone is sufficient for the image.
Playwright documents navigation conditions including commit, domcontentloaded, and load. These describe navigation progress, not necessarily that application-specific content has rendered. Playwright discourages using networkidle as a testing readiness strategy and recommends web assertions to assess readiness. An application may continue making background requests after the relevant content is already visible, or appear quiet before the element you need has rendered.
Write the image or use its returned data
With { path: 'dashboard.png' }, the awaited call writes the screenshot to that path. If you omit path, retain the returned buffer and pass it to the next operation only after the await completes. For example, you can return the buffer from a helper or send it to another service; the important rule is not to treat an unresolved Promise as image data.
Rank #2
async function captureDashboard(page) {
await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
return await page.screenshot({ fullPage: true });
}
Callers of captureDashboard should also await it before consuming the result:
const image = await captureDashboard(page);
Wait for navigation caused by an action
If a click or submit causes navigation, coordinate the navigation wait with the action, then verify the destination. Playwright recommends waitForURL() over waitForNavigation(); its documentation calls waitForNavigation inherently racy. Starting the URL wait before the action avoids missing a fast transition:
const urlWait = page.waitForURL('**/reports');
await page.getByRole('link', { name: 'Reports' }).click();
await urlWait;
await page.getByRole('heading', { name: 'Reports' }).waitFor();
await page.screenshot({ path: 'reports.png' });
Adjust the URL pattern and readiness locator to the application. A URL change confirms the navigation target, while the final UI wait confirms the element your screenshot needs. Do not substitute a navigation wait for a page-specific condition when the image depends on data rendered after navigation.
Use Puppeteer when your project already uses it
Puppeteer’s Page.screenshot() also returns a Promise. Await it before writing or processing the image. By default, its result is a Uint8Array; it can return a base64 string when configured to do so.
Rank #3
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.locator('h1').wait();
const image = await page.screenshot({ fullPage: true });
// Use or write image only after screenshot() resolves.
} finally {
await browser.close();
}
})();
The example uses Puppeteer’s locator wait to make the required heading part of the capture flow. Confirm the precise locator and screenshot options against the Puppeteer version installed in your project; API details can change. The available documentation does not establish that Playwright or Puppeteer is universally faster or better. Prefer the framework your application already uses, and choose based on its browser support and the work you need to do with the output.
Puppeteer documents that creating a new page or closing a page in the same BrowserContext automatically waits for an in-progress screenshot to finish. Do not treat bringToFront() as such a wait: it does not wait for the capture to complete. Explicitly awaiting the screenshot call remains the clearest way to ensure your own code handles the finished image.
For repeatable visual regression checks
A one-off call to page.screenshot() saves an image; it does not tell you whether the page looks correct. For screenshot comparisons in Playwright Test, use await expect(page).toHaveScreenshot(). The assertion waits for two consecutive screenshots to produce the same result and compares the last image with the expectation. It is a Playwright Test feature, not a general replacement for saving screenshots in a script.
Use a regular screenshot when you need an image file or image bytes. Use the test assertion when the goal is to detect a visual difference against an expected image. In either case, establish the intended application state before capture so the comparison is about the page you mean to test.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsChoosing capture options
Keep the capture configuration focused on what the consumer needs. Playwright’s screenshot API supports saving to a path, full-page capture, clipping, output format, timeout, and cancellation. Check the API reference for the installed version when you need less common options; the code examples here use only the path and full-page options.
- Viewport or full page: the default is the visible viewport. Set
fullPage: truewhen the image should include content beyond the current viewport. - File or in-memory result: provide a path to write the image, or omit it and handle the returned image data after awaiting the call.
- Specific region: use clipping when only a defined area belongs in the output.
- Output requirements: select an output format when the downstream process requires a particular image type.
- Limits and cancellation: set timeout or cancellation behavior only when your workflow needs it, and verify the relevant option semantics in the API version you use.
Common problems and fixes
The screenshot is blank or missing page content
Cause: navigation completed, but the application had not rendered the specific content by capture time. Fix: wait for the relevant heading, chart, image, or other page condition before calling screenshot(). Do not assume that waiting for a generic navigation event proves application readiness.
The image is saved before it is complete
Cause: the code starts the screenshot but does not await it before continuing. Fix: use await page.screenshot(...), or return the Promise from an async helper and await that helper at its call site.
The expected file was not created
Cause: the screenshot was requested without a path, or the code assumed the returned image data would be saved automatically. Fix: provide a file path if the API should write the file. Otherwise, retain and explicitly write or pass along the returned image data after awaiting capture.
Free tools Windows power users keep installed
One-click scans. No signup required.
The script waits for the wrong navigation
Cause: the code relies on a broad or racy navigation wait while an action changes the URL. Fix: create a waitForURL() wait before triggering the action, await it, and then wait for the page-specific element needed in the screenshot.
The page never becomes idle
Cause: the application may keep background network activity running. Fix: use a web assertion for the content the image requires rather than treating networkidle as a general readiness signal.
The capture test is flaky
Cause: the screenshot is being taken before the expected UI is stable, or the task needs a visual comparison rather than a file capture. Fix: wait for an explicit page condition; for visual regression in Playwright Test, use toHaveScreenshot(), which waits for consecutive stable screenshots before comparing.
Or skip the browser setup
If you need an image from a URL rather than a browser automation flow, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. Its clean-shot steps accept cookie and consent banners like a visitor and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
Here is the cURL call, using the documented endpoint and parameters. Replace the URL with the page to capture and use your API key:
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://stripe.com
-o shot.webp
See the ScreenshotNeo documentation for request options. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for ScreenshotNeo’s free plan.
Frequently Asked Questions
Does awaiting a screenshot block the Node.js event loop?
No. Await pauses the surrounding async function until the screenshot Promise resolves; it does not synchronously block the event loop.
Can I take multiple screenshots at once?
The cited API documentation does not establish a general concurrency limit. If you capture multiple pages concurrently, check the limits and resource needs of your browser setup and the API version you use.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

