ScreenshotNeo

BlogHow-to

How to Take a Screenshot of a Private Page Behind a Login with an Iframe

Authenticate the browser context, wait for the iframe’s content, and capture the rendered page with Playwright. Includes runnable setup, edge cases, and troubleshooting.

By the ScreenshotNeo team4 October 20268 min read

Direct answer: Sign in using the same browser context that will take the screenshot, open the page, wait for a meaningful element inside the iframe, then capture the rendered page or a specific visible element. Use a frame locator to interact with iframe content; you do not need to extract the iframe’s DOM just to capture what the browser displays.

This guide uses Playwright with JavaScript. The example uses a normal sign-in flow and illustrative selectors: adapt the URL, credentials, frame selector, and readiness condition to your authorized account and application. No particular site’s login or iframe behavior is guaranteed.

1. Set up Playwright

Use Node.js and install Playwright. The following commands create a small project and install the Chromium browser used by the script:

mkdir iframe-screenshot
cd iframe-screenshot
npm init -y
npm install playwright
npx playwright install chromium

Store credentials in environment variables rather than writing them into the script. For example, in a Unix-like shell:

export LOGIN_URL='https://example.com/login'
export TARGET_URL='https://example.com/private/report'
export LOGIN_USER='your-authorized-username'
export LOGIN_PASSWORD='your-authorized-password'

Use the site’s normal authentication process. Some sites require SSO, MFA, a one-time challenge, or an interactive sign-in; a simple username-and-password form will not cover every flow.

2. Sign in, wait for the iframe, and capture

Save this as screenshot-private-iframe.mjs. Replace the example selectors with stable selectors from your own application. The frame selector identifies the iframe element in the parent page; the heading locator is an example of a meaningful readiness signal inside it.

import { chromium } from 'playwright';

const loginUrl = process.env.LOGIN_URL;
const targetUrl = process.env.TARGET_URL;
const username = process.env.LOGIN_USER;
const password = process.env.LOGIN_PASSWORD;

if (!loginUrl || !targetUrl || !username || !password) {
  throw new Error('Set LOGIN_URL, TARGET_URL, LOGIN_USER, and LOGIN_PASSWORD.');
}

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  viewport: { width: 1440, height: 1000 },
  deviceScaleFactor: 1
});
const page = await context.newPage();

try {
  await page.goto(loginUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
  await page.getByLabel('Email').fill(username);
  await page.getByLabel('Password').fill(password);
  await page.getByRole('button', { name: /sign in|log in/i }).click();

  // Wait for an application-specific signal that login completed.
  await page.getByRole('navigation').waitFor({ state: 'visible', timeout: 30000 });

  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });

  const frame = page.frameLocator('iframe[data-testid="private-content"]');
  await frame.getByRole('heading', { name: /report/i }).waitFor({
    state: 'visible',
    timeout: 30000
  });

  // Captures the rendered top-level page, including visible iframe content.
  await page.screenshot({ path: 'private-page.png', fullPage: true });
  console.log('Saved private-page.png');
} finally {
  await browser.close();
}

Run it with node screenshot-private-iframe.mjs. A reliable readiness condition is tied to the content you need, such as a report heading, rather than simply assuming that the parent page’s navigation event means the embedded document is ready.

3. Choose what to capture

Goal Capture approach Notes
Visible browser page page.screenshot({ path: 'page.png' }) Captures the current viewport.
Page including content below the fold page.screenshot({ path: 'page.png', fullPage: true }) Useful for long pages. Very tall pages can produce large images and take longer to capture.
One visible region await page.locator('.report-panel').screenshot({ path: 'panel.png' }) The locator must resolve to a visible element. For an element inside an iframe, locate it through the frame locator, then capture the locator if supported by your Playwright version and setup.
Interact with iframe controls const frame = page.frameLocator('iframe'); Use the frame locator to select and operate on elements inside the frame. Frame targeting and screenshot capture are separate tasks.

Playwright documents page and full-page screenshots, format and scale options, and element screenshots in its screenshot documentation. Its frame documentation explains frame locators and iframe targeting. Screenshot formats include PNG, JPEG, and, where supported by the API, WebP. Choose the format and options supported by the installed Playwright version.

4. Reuse an authorized login session

If repeated runs should not sign in every time, Playwright can save and reuse browser authentication state. Treat the state file like a credential: it can contain cookies or other session data that grants access.

// After completing the authorized login flow:
await context.storageState({ path: 'playwright/.auth/state.json' });

// In a later run, create a context using the saved state:
const context = await browser.newContext({
  storageState: 'playwright/.auth/state.json',
  viewport: { width: 1440, height: 1000 }
});

Keep the file out of source control and restrict access to it. Authentication state can expire or be invalidated. SSO, MFA, account rules, and site-specific bot controls may require a fresh interactive login. See Playwright’s authentication guidance.

5. Understand iframe and authentication limits

  • The iframe may have a separate login. Signing into the host page does not necessarily authenticate the embedded service. Check the frame URL and complete the embedded service’s authorized login flow if required.
  • Cross-origin frames limit page JavaScript. The browser’s same-origin security model restricts a page from reading another origin’s frame DOM directly. Browser automation provides frame-targeting interfaces for selecting and interacting with frame content.
  • A screenshot captures rendered pixels. You generally do not need to copy the iframe DOM into a canvas to screenshot what the browser renders.
  • The site can block embedding. The embedded service or host may enforce frame-ancestor or X-Frame-Options rules, cookie requirements, or origin restrictions. Check the site’s documentation or administrator when the frame refuses to load.
  • Wait for the right state. A parent document can finish loading while a client-rendered iframe is still loading or updating. Wait for the specific frame content you need.

6. Troubleshoot common failures

Symptom Likely cause Fix
The screenshot shows a login page The browser context is unauthenticated, the login redirect is incomplete, or the iframe requires separate authentication. Confirm login succeeded in the same context used to capture. Check the frame URL and complete any required embedded-service login.
Frame locator times out The iframe selector is wrong, the frame has not attached, or the expected element is not present. Use a stable iframe selector; inspect the page and frame URL; wait for a specific in-frame element with an appropriate timeout.
The screenshot omits the content The content is below the viewport, nested in another frame, or still loading. Wait for the content, check for nested frames, and use full-page capture when the content is below the fold.
The embedded service refuses to load Embedding, cookie, origin, or frame policy restrictions may apply. Check the service’s embedding requirements and the browser console/network errors; consult the site owner if policy blocks the frame.
Login works locally but fails in automation The flow may require MFA, SSO, a user interaction, or an account-specific policy. Use the site’s supported sign-in process and authorized state reuse. Do not bypass access controls.
Capture is slow or inconsistent The page or iframe may load data asynchronously, or the script may wait on an event that does not reflect content readiness. Wait on a meaningful content locator, use bounded timeouts, and avoid relying on network-idle alone for applications with ongoing requests.

7. Performance, reliability, and cost

Browser automation runs a browser and loads the target page, so its time and resource use depend on the page, login flow, assets, and capture dimensions. A locator-based wait can avoid both capturing too early and waiting on unrelated activity. Full-page images of long pages require more work and produce larger files than viewport or element captures.

For repeated jobs, reuse authorized authentication state where appropriate, set explicit timeouts, and verify the output for a login prompt, loading state, blocked-frame message, or stale content. Plan for session expiry and site-side changes to selectors or authentication steps. Your costs depend on where and how you run the browser automation; the Playwright APIs described here do not imply a particular hosting price.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request captures a public URL as an image or PDF. For a private page, its request must be able to access the page; a screenshot service cannot make an unauthorized page public or bypass a login. If your authorized page is available to the capture request, this is the one-call form. 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://example.com/report -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/report"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/report'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

The Node.js snippet uses Bun’s file-writing helper. With Node.js, save the response body using fs instead:

import { writeFile } from 'node:fs/promises';

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/report' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers identify the page verdict and billing status.
  • An MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots, inspect page information, and capture PDFs.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.

ScreenshotNeo provides a capture request for a URL; it does not replace the authorized browser login workflow shown above when the target is private or requires a session that the request cannot access. Sign up for 1,000 free screenshots a month, with no card.

FAQ

Do I need to access the iframe DOM to take a screenshot?

No. To capture the browser-rendered page, use the page screenshot API. Use frame targeting when you need to select, inspect, or interact with elements inside the iframe.

Can the top-level page’s login authenticate the iframe?

Sometimes, but it depends on the embedded service and its authentication setup. Verify the iframe itself shows the expected authenticated content.

Can a page script capture the screen from inside an iframe?

The Screen Capture API is separate from Playwright’s browser screenshot API. When used from an iframe, its display-capture permission can be controlled by the Permissions Policy header or the iframe’s allow attribute. See the Screen Capture API documentation.

What should I check before sharing a screenshot?

Confirm it contains the intended current data and does not expose credentials, personal information, or other private content that should not be shared.