ScreenshotNeo

BlogHow-to

Can Playwright Capture Screenshots of Pages with Cross-Origin Iframes?

Yes. A Playwright page screenshot captures the rendered pixels of a visible cross-origin iframe, even though screenshotting does not mean your code can read its DOM.

By the ScreenshotNeo team4 October 20266 min read

Yes. A Playwright page screenshot captures the page as rendered, so visible pixels inside an embedded cross-origin iframe appear in the image. This follows from the page-level screenshot API and rendered-page model; Playwright’s documentation does not phrase it as a separate cross-origin screenshot guarantee. A screenshot does not show that your test can read or manipulate the iframe’s document.

1. Capture the rendered page

For a normal screenshot, navigate to the page, wait for the content you need, then call page.screenshot(). The iframe must have loaded and be visible at capture time for its rendered pixels to appear.

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

test('captures a page containing an embedded frame', async ({ page }) => {
  await page.goto('https://example.com');
  await page.screenshot({ path: 'page.png' });

  // For visual regression instead of just saving an image:
  // await expect(page).toHaveScreenshot();
});

example.com is illustrative; this example does not claim that the site contains a cross-origin iframe. For an actual target, replace it with the URL of the page under test. The screenshot is the browser-rendered page image; it is not a serialized copy of each frame’s DOM.

Viewport or full-page capture

// Capture the current viewport.
await page.screenshot({ path: 'viewport.png' });

// Capture the full scrollable page.
await page.screenshot({ path: 'full-page.png', fullPage: true });

A full-page screenshot requests the whole scrollable page. It does not make the frame’s DOM accessible. For a lazy-loaded iframe or content inside a frame that only appears after scrolling, make sure it has actually loaded and rendered before capture. Full-page capture can also produce a very tall image, depending on the page.

Wait for the page and frame to be ready

Navigation completion alone may not mean that a third-party iframe has finished loading its content. If you know a stable element in the frame, wait for it through a frame locator. Use a selector that uniquely identifies the intended iframe when the page has several.

await page.goto('https://your-site.example');

const frame = page.frameLocator('iframe[title="Embedded content"]');
await frame.getByRole('heading', { name: 'Ready' }).waitFor();
await page.screenshot({ path: 'page.png', fullPage: true });

The selector and heading above are examples; use attributes and content that exist on your target. If you do not need to interact with the iframe, you can still take a page screenshot without locating its DOM. Waiting for a known visual element is useful when you need to avoid capturing a loading state.

2. Screenshot the iframe versus work with its DOM

These are separate tasks:

Goal Approach
Save the whole rendered page, including visible frame pixels page.screenshot()
Save the full scrollable page page.screenshot({ fullPage: true })
Find or interact with content inside a frame Use Playwright’s page.frame() or page.frameLocator() APIs
Compare a page image against a visual baseline Use expect(page).toHaveScreenshot()

For example, a frame locator can target a button inside a uniquely selected iframe:

const checkout = page.frameLocator('iframe[title="Checkout"]');
await checkout.getByRole('button', { name: 'Continue' }).click();
await page.screenshot({ path: 'after-click.png' });

Choose a selector that identifies the correct iframe. Frame automation is for locating and acting on frame content; a page screenshot is for capturing what the browser rendered. The reviewed Playwright documentation describes the frame APIs, but does not fully spell out browser same-origin access semantics, so do not infer arbitrary script access to a cross-origin document from the fact that its pixels appear in a screenshot.

3. Use screenshots for visual regression

For a visual regression test, Playwright provides toHaveScreenshot(). The assertion waits for two consecutive screenshots to match before comparing with the expected snapshot. This helps avoid capturing while the page is still changing, though it cannot make dynamic third-party content deterministic by itself.

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

test('page with embedded content matches its visual baseline', async ({ page }) => {
  await page.goto('https://your-site.example');
  await expect(page).toHaveScreenshot('page-with-frame.png');
});

Keep the browser, operating system, browser version, settings, hardware, power conditions, and headless mode consistent between baseline creation and comparison. Playwright documents these as sources of screenshot variation. A third-party iframe may also show changing content, so use stable test data or exclude that area when the test is meant to focus on the surrounding page.

Control or hide frame content in comparison snapshots

Screenshot styles can apply to inner frames. For example, if the iframe is not part of the behavior under test, a screenshot stylesheet can hide it to prevent its changing content from affecting the image:

await expect(page).toHaveScreenshot('page-without-iframe.png', {
  style: 'iframe { visibility: hidden !important; }',
});

Use this only when excluding the frame is appropriate for the assertion. If the purpose is to verify the embedded content, keep it visible and stabilize its state instead.

4. Troubleshooting

Symptom Likely cause What to do
The iframe area is blank The iframe has not loaded, is hidden, or the embedded site did not render. Wait for a stable element inside the frame with frameLocator(), check that the frame is visible, and capture after it is ready.
The screenshot has a loading state The capture ran before asynchronous frame content settled. Wait for a relevant frame element or a page-specific ready condition before capturing.
A frame locator cannot find an element The selector matches the wrong iframe, the target is not present yet, or the frame content differs from the assumed markup. Use a unique iframe selector, inspect the actual frame structure, and wait for the intended element before interacting.
Visual snapshots fail intermittently Dynamic iframe content or a changed browser/host environment creates pixel differences. Stabilize test data and capture conditions. Hide the iframe with screenshot styles only if its appearance is outside the test’s scope.
The page screenshot contains only the viewport The screenshot call did not request full-page capture. Set fullPage: true when you want the full scrollable page.
Code assumes a screenshot grants frame DOM access Rendered pixels and DOM automation were treated as the same capability. Use page screenshots for pixels and Playwright frame APIs for supported frame interaction; do not rely on screenshot output to access a document.

5. Performance, reliability, and cost

Page and full-page screenshots run in the browser process and produce image data. Full-page captures may take longer and use more memory as the page grows. Waiting for third-party frames also makes capture time depend on those sites and their network conditions. Keep the capture scope to what the test needs, and avoid waiting for unrelated content.

For reliable results, use stable frame content, wait for a meaningful ready signal, and keep the visual comparison environment consistent. If a third-party frame is unavailable, blocked, or still loading, the screenshot reflects the browser’s rendered state at capture time; it cannot guarantee the embedded site completed successfully.

Playwright itself is an open-source browser automation framework. Running it has infrastructure costs such as browser execution and CI time; the research sources provide no cross-origin screenshot success benchmark or fixed cost figure. Account for retries and large full-page images in your own pipeline.

6. Or skip the browser setup

For a one-call website capture, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from a URL. It can capture the rendered page, including visible embedded-frame pixels when the frame has rendered. See the ScreenshotNeo API documentation for request options.

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}`);
  • Cookie banners are accepted and removed before the shot; known newsletter popups and chat widgets are removed too, and each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers say the page verdict and whether the capture was billed.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for free and get 1,000 screenshots a month with no card.

7. FAQ

Will an iframe show up in a full-page screenshot?

Its rendered pixels should appear wherever the iframe is visible in the captured page. Use fullPage: true to request the entire scrollable page.

Can Playwright screenshot an iframe without accessing its DOM?

Yes. A page-level screenshot captures rendered pixels; frame DOM interaction is a separate task.

Does toHaveScreenshot() wait for an iframe?

It waits for consecutive screenshots to match before comparing. For a specific frame state, explicitly wait for the relevant content before the assertion.

Can I remove an iframe from a visual snapshot?

Yes. Screenshot styles can affect inner frames, including hiding iframe elements. Use that when the frame is outside the behavior being tested.