Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Pass an absolute filesystem path to Playwright’s path option. Resolve the destination before calling page.screenshot(), create its parent directory when necessary, and use fullPage: true (JavaScript/TypeScript) or full_page=True (Python) when you need the entire scrollable document rather than only the current viewport.

The direct answer

In JavaScript or TypeScript, build an absolute path with Node’s path.resolve() and pass it to page.screenshot({ path }):

import path from 'node:path';

const outputPath = path.resolve(process.cwd(), 'artifacts', 'page.png');
await page.screenshot({ path: outputPath, fullPage: true });

In Python, resolve a pathlib.Path and convert it to a string:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from pathlib import Path

output_path = (Path.cwd() / 'artifacts' / 'page.png').resolve()
await page.screenshot(path=str(output_path), full_page=True)

Playwright’s API accepts a string path. If the path is relative, Playwright resolves it from the process’s current working directory. An absolute path removes that ambiguity when a script can be launched from different directories, a CI runner, an IDE, or a test worker.

Choose the kind of screenshot you actually need

  • Viewport screenshot: call page.screenshot() without a full-page option. It captures the currently visible viewport.
  • Full-page screenshot: set fullPage: true in JavaScript/TypeScript or full_page=True in Python. Playwright captures the complete scrollable page.
  • Element screenshot: use a locator’s screenshot() method when only one component, such as a header or chart, belongs in the file.
  • Visual snapshot: use expect(page).toHaveScreenshot() for reference-image assertions. Those files follow Playwright Test’s snapshot-directory rules rather than an arbitrary application path.

JavaScript and TypeScript: save to a deterministic absolute path

Create the directory and capture a full page

The parent directory must exist before Playwright writes the image. This example creates nested directories safely and then captures a full-page PNG:

import fs from 'node:fs/promises';
import path from 'node:path';
import { chromium } from 'playwright';

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

await page.goto('https://example.com', { waitUntil: 'networkidle' });

const outputPath = path.resolve(
  process.cwd(),
  'artifacts',
  'screenshots',
  'example-home.png'
);
await fs.mkdir(path.dirname(outputPath), { recursive: true });

await page.screenshot({
  path: outputPath,
  fullPage: true
});

console.log(`Saved screenshot to ${outputPath}`);
await browser.close();

The filename extension controls the image type. Use a name ending in .png, .jpeg, or .webp for the corresponding output format. Keep the extension and your intended format aligned so downstream tools know what they are opening.

Use a project-root path

process.cwd() is the directory from which Node was started. If your command is always run from the project root, resolving from it produces a predictable location such as /workspace/my-app/artifacts/screenshots/example-home.png. If the script may be started elsewhere, derive the base directory from your application configuration instead of assuming the shell’s current directory.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Capture one element

For a component rather than the whole document, resolve the path in the same way and call the locator method:

import fs from 'node:fs/promises';
import path from 'node:path';

const outputPath = path.resolve(process.cwd(), 'artifacts', 'header.png');
await fs.mkdir(path.dirname(outputPath), { recursive: true });

await page.locator('header').screenshot({ path: outputPath });

The locator must resolve to the intended element. If a selector matches multiple elements, narrow it with a role, test id, or another specific locator.

Python: resolve with pathlib

Async API example

Python’s equivalent is Path.resolve(). Create the parent directory with mkdir(parents=True, exist_ok=True):

from pathlib import Path
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()
        await page.goto("https://example.com", wait_until="networkidle")

        output_path = (
            Path.cwd() / "artifacts" / "screenshots" / "example-home.png"
        ).resolve()
        output_path.parent.mkdir(parents=True, exist_ok=True)

        await page.screenshot(path=str(output_path), full_page=True)
        print(f"Saved screenshot to {output_path}")
        await browser.close()

The synchronous API uses the same options, but without await. The important details are still the resolved path, an existing parent directory, and full_page=True when the screenshot must include content below the fold.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Capture one element in Python

output_path = (Path.cwd() / "artifacts" / "header.png").resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
await page.locator("header").screenshot(path=str(output_path))

Playwright Test: use the test-scoped output directory

When the code runs under Playwright Test, testInfo.outputPath() is usually preferable to constructing a global artifact directory yourself. It gives each test a path inside the runner’s output area and keeps files associated with the test that produced them:

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

test('capture homepage', async ({ page }, testInfo) => {
  await page.goto('https://example.com');
  await page.screenshot({
    path: testInfo.outputPath('homepage.png'),
    fullPage: true
  });
});

This is still an explicit path, but its location is controlled by the test runner. It is useful for reports, retries, parallel workers, and CI cleanup. Do not confuse this with a normal application screenshot path: the runner may remove or relocate its output according to your test configuration.

Snapshot assertions use a different path model

expect(page).toHaveScreenshot() is for visual comparisons. Playwright stores reference images in the snapshots area for the test file, and the snapshot path must remain within that snapshots directory. If your repository needs a stable naming convention, configure snapshotPathTemplate (or the assertion-specific path template) rather than passing an arbitrary absolute artifact path to a normal screenshot call.

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

test('visual baseline', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot('homepage.png');
});

Use page.screenshot({ path }) for a file your application or pipeline owns. Use toHaveScreenshot() when the file is a baseline that should be compared and versioned as part of a visual test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why a screenshot appears in the wrong folder

The path is relative

A value such as screenshots/home.png is interpreted relative to the process’s current working directory, not necessarily the directory containing your source file. Print process.cwd() (Node) or Path.cwd() (Python) to see the base directory, then switch to path.resolve() or Path.resolve().

The parent directory does not exist

Playwright writes the file but does not create every missing parent directory for you. Call fs.mkdir(path.dirname(outputPath), { recursive: true }) in Node or output_path.parent.mkdir(parents=True, exist_ok=True) in Python before the screenshot.

The test runner controls the output

If the screenshot is produced by a fixture, trace, reporter, or testInfo.outputPath(), its location follows Playwright Test’s configured output directory. Check the test report and runner configuration instead of searching relative to your source file.

A snapshot assertion is being mistaken for an artifact

Baseline images from toHaveScreenshot() belong in the snapshots hierarchy. Configure the snapshot template if you need deterministic placement; do not treat the baseline path as an ordinary screenshot destination.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The filename implies another format

Playwright infers the image type from the extension. A file named capture.webp is not the same output as capture.png. Make the extension match the format expected by your viewer, comparison tool, or upload step.

Reliable full-page captures

Wait for the page state you need

A full-page option controls the capture area, not whether the page’s data has finished rendering. Wait for a suitable navigation state, a specific locator, or an application-ready condition before taking the shot. For pages that lazy-load content, scroll or otherwise trigger the application’s loading behavior before capture when necessary.

Keep paths unique in parallel runs

Two workers writing the same absolute filename can overwrite each other. Include a test name, URL slug, timestamp, or worker identifier in the filename, or use testInfo.outputPath() so the runner scopes files per test.

Check permissions and platform differences

An absolute path can still fail if the process lacks write permission, the drive is read-only, or the path uses invalid characters for the operating system. Prefer path-joining APIs over manually concatenating separators, and log the final resolved path before capture in CI.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Manage very long pages

Full-page images can be substantially larger than viewport images and may take longer to encode or upload. Capture only the required element when possible, choose an appropriate image format, and avoid generating duplicate full-page files in every retry unless the evidence is needed.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF, so you do not need to install Playwright or manage browser binaries for a straightforward URL capture. The API accepts an absolute target URL and returns the image bytes.

See the ScreenshotNeo documentation for the current options. A minimal cURL call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in Node.js:

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 failed: ${res.status}`);
const bytes = await res.arrayBuffer();
await Bun.write('shot.webp', bytes);

ScreenshotNeo can accept cookie and consent banners before capture, then remove more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For more control, it supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and page settings, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for selectors/delays/network idle, request and resource blocking, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and familiar parameter names for easier migration.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is included on every plan. Create a free ScreenshotNeo account to get started.

Quick decision guide

Need Use Path behavior
A file your script controls page.screenshot({ path }) Use an absolute path; create the parent directory.
The complete scrollable page fullPage: true or full_page=True Same destination rules as a normal screenshot.
One component locator.screenshot() Pass the resolved file path to the locator method.
Test-run evidence testInfo.outputPath() Runner-managed, test-scoped output directory.
Visual regression baseline toHaveScreenshot() Snapshots directory and configured snapshot templates.

Frequently Asked Questions

Does Playwright create the screenshot directory automatically?

Create the parent directory in your application code with Node’s recursive mkdir or Python’s Path.mkdir before calling the screenshot method.

Can an absolute path be used with a locator screenshot?

Yes. Resolve the destination exactly as you would for page.screenshot(), then pass it to locator.screenshot().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What should I use for screenshots attached to a Playwright Test run?

Use testInfo.outputPath(‘filename.png’) so the runner keeps the artifact with the test’s output.

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.