ScreenshotNeo

BlogHow-to

How to Take a Playwright Screenshot of a Canvas Element

Capture a canvas with Playwright using a locator screenshot. Learn how to wait for drawing, choose image options, handle common failures, and save or process the result.

By the ScreenshotNeo team4 October 20267 min read

Use Playwright’s locator screenshot API to capture the rendered page region occupied by a canvas:

await page.locator('canvas').screenshot({ path: 'canvas.png' });

Playwright scrolls the matched element into view and performs its actionability checks before capture. The screenshot reflects what the browser renders in the canvas’s bounds, including any page content that covers it. Wait for your application to finish drawing before capture; the correct readiness signal depends on the application. See the Playwright screenshots guide and locator screenshot API.

1. Install Playwright and capture a canvas

This complete JavaScript example uses Playwright Test. Install the test package and browser, then save the test as canvas.spec.js:

npm init playwright@latest
npx playwright install
import { test, expect } from '@playwright/test';

 test('capture the rendered canvas', async ({ page }) => {
  await page.goto('https://example.com/app');

  // Replace this selector if the page has multiple canvases.
  const canvas = page.locator('canvas#chart');
  await expect(canvas).toBeVisible();
  await canvas.screenshot({ path: 'artifacts/canvas.png' });
});

Run it with:

npx playwright test canvas.spec.js

Make sure the output directory exists if your setup does not create it. A locator such as canvas#chart or [data-testid="drawing-surface"] is safer than a bare canvas when a page contains several canvases.

2. Wait until the application has drawn

Element actionability and visibility do not tell Playwright that an application-specific canvas rendering process has finished. If you capture too early, the image may be blank or incomplete. Wait for an observable signal your app controls, such as a ready attribute or a completion event exposed by the page.

For example, if the application sets data-render-state="ready" after drawing:

const canvas = page.locator('canvas#chart');
await expect(canvas).toHaveAttribute('data-render-state', 'ready');
await canvas.screenshot({ path: 'artifacts/chart.png' });

Alternatively, if the application emits a browser event when rendering completes, register the wait before triggering the render:

const rendered = page.evaluate(() => new Promise((resolve) => {
  window.addEventListener('chart-rendered', resolve, { once: true });
}));
await page.getByRole('button', { name: 'Render chart' }).click();
await rendered;
await page.locator('canvas#chart').screenshot({ path: 'artifacts/chart.png' });

Adapt the event and trigger to the application. A fixed delay can be useful for a known animation or debounce, but it is less reliable than waiting for a meaningful ready signal:

await page.waitForTimeout(500);
await page.locator('canvas#chart').screenshot({ path: 'artifacts/chart.png' });

3. Choose file output or a buffer

Pass path to save the screenshot. Omit it to receive image bytes in Node.js, which you can pass to an image-processing or visual-diff step:

const imageBuffer = await page.locator('canvas#chart').screenshot();
// Pass imageBuffer to your image-processing or visual-diff code.

The Python API offers the same element screenshot pattern and returns bytes when no path is supplied:

from pathlib import Path
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://example.com/app')
    canvas = page.locator('canvas#chart')
    canvas.screenshot(path='artifacts/chart.png')
    image_bytes = canvas.screenshot()
    Path('artifacts/chart-copy.png').write_bytes(image_bytes)
    browser.close()

Create the artifacts directory before running the Python example if it does not already exist. Playwright’s Python documentation describes element screenshots in its screenshots guide.

4. Set image format, scale, and repeatability options

PNG is the documented default. Locator screenshots also support JPEG and WebP; JPEG quality can be set when using JPEG. Choose a format based on what consumes the image: PNG is useful for crisp graphics, while JPEG and WebP can reduce file size where lossy compression is acceptable.

await page.locator('canvas#chart').screenshot({
  path: 'artifacts/chart.webp',
  type: 'webp'
});

await page.locator('canvas#chart').screenshot({
  path: 'artifacts/chart.jpg',
  type: 'jpeg',
  quality: 85
});

The scale option controls output resolution:

  • scale: 'css' produces one image pixel per CSS pixel.
  • scale: 'device' follows the device pixel ratio and can produce a larger, more detailed image on high-density displays.
await page.locator('canvas#chart').screenshot({
  path: 'artifacts/chart-compact.png',
  scale: 'css'
});

Other documented screenshot controls include animation handling, masking, background handling, screenshot styles, and timeouts. Use them when they suit the page and your Playwright version. They can help make captures repeatable, but they do not establish that arbitrary canvas drawing has completed; keep the application readiness wait as a separate step.

5. Understand what the capture contains

locator.screenshot() captures the rendered page region corresponding to the matched canvas’s bounds. It is a browser screenshot of that region, not a direct export of the canvas bitmap.

  • Overlays: If a dialog, tooltip, or other element covers the canvas, that covered page content can appear in the capture. Close the overlay or adjust the page state if you need an unobstructed result.
  • Scroll position: Playwright scrolls the element into view before capture. For a scrollable element, the screenshot reflects the currently scrolled content.
  • CSS and composition: The element screenshot captures the page’s rendered presentation. If you instead need the raw canvas bitmap, that is a different operation using browser-side canvas APIs such as toDataURL(); it does not capture surrounding page composition.
  • Multiple canvases: A broad selector can match the wrong one or be ambiguous. Use a locator tied to a stable id, class, accessible context, or test id.

6. Troubleshoot common problems

Symptom Likely cause Fix
Screenshot is blank or only partly drawn The application had not finished rendering when capture began. Wait for an app-specific ready state or completion event before calling screenshot().
Strict mode or locator error The selector matches more than one canvas, or no canvas. Make the locator specific and check that the target exists before capture.
Element is not visible or cannot be captured The canvas is hidden, detached, or otherwise not actionable. Wait for the correct page state, ensure the canvas is attached and visible, and inspect whether navigation replaced it.
Unexpected dialog or tooltip in the image Another element overlaps the canvas bounds. Dismiss or hide the overlay before capture, or use a capture method appropriate to the bitmap-only requirement.
Output looks blurry or dimensions differ The selected scale does not match the desired pixel dimensions or device pixel ratio. Choose CSS scale for layout-sized output or device scale for device-resolution output, then inspect the resulting dimensions.
Screenshot times out The locator does not become actionable in time, or the page is still transitioning. Check selector and visibility, wait for the page’s actual ready state, and adjust the screenshot timeout only when the page legitimately needs longer.
File cannot be written The destination directory is missing or the process lacks write access. Create the directory and use a writable path, or omit path and handle the returned bytes.

7. Performance, reliability, and cost

A locator screenshot captures only the element’s bounds, which is generally a smaller artifact than a full-page capture. Output dimensions and encoding affect memory, processing time, and file size; device scale may produce substantially more pixels than CSS scale. Select only the resolution and format your downstream workflow needs.

For reliable automation, use a stable selector, wait for an application-owned rendering signal, and keep viewport, device scale factor, and page state consistent across runs. Avoid treating a fixed sleep as proof that rendering is complete. Screenshot capture itself uses your Playwright browser process and infrastructure; costs depend on where and how you run that infrastructure.

Or skip the browser setup

If you need a screenshot of the whole rendered page that contains the canvas, ScreenshotNeo can capture it with one request. It is a website screenshot API and MCP server; it does not target a canvas selector, so use Playwright when you specifically need only the canvas element.

See the ScreenshotNeo API documentation for parameters and options.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/app"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/app'
});
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(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000. Sign up for free and get 1,000 screenshots a month with no card.

Frequently asked questions

Which Playwright version supports locator screenshots?

The locator screenshot API has been available since Playwright v1.14. Check the API reference for the behavior and options supported by the version installed in your project.

Does a canvas need to be scrolled into view first?

Playwright scrolls the matched element into view as part of locator screenshot behavior.

Can I get screenshot bytes without creating a file?

Yes. Omit the path option; Node.js returns a buffer and Python returns bytes.

Does a locator screenshot export the original canvas pixels?

No. It captures the rendered page region at the element’s bounds. A canvas bitmap export is a separate browser-side operation.