Playwright Screenshot Captures the Wrong Tab: How to Select the Page
Playwright captures the Page object you call screenshot() on. Learn how to identify the right tab or popup, wait for it to be ready, and troubleshoot mismatches.
Playwright captures the Page object you call screenshot() on. To capture a specific tab, keep a reference to that tab’s Page and call the method on that object. If the target is a popup, wait for its new Page and capture that page. You do not need to bring a page to the foreground.
1. Understand what Playwright captures
A Playwright Page represents one browser tab or popup. A BrowserContext can contain multiple pages, and each page has its own URL and state. A screenshot call does not choose a page based on which tab looks active in the browser window: the object before .screenshot() determines the source.
For example, page.screenshot() captures page. If the test opens a second page and you mean to capture it, use the reference for that second page. Bringing a page to the front is not required for normal Playwright interaction or screenshots.
2. Capture a page you created
Keep the same reference for navigation, checks, and capture. This complete Node.js example uses Playwright’s test runner:
import { test, expect } from '@playwright/test';
test('captures the intended page', async ({ browser }) => {
const context = await browser.newContext();
const accountPage = await context.newPage();
await accountPage.goto('https://example.com');
await expect(accountPage).toHaveURL('https://example.com/');
await expect(accountPage.getByRole('heading')).toBeVisible();
await accountPage.screenshot({ path: 'account.png' });
await context.close();
});
If you already have a page from a fixture, use that fixture’s page consistently. Avoid reassigning a generic variable such as page when another tab opens; descriptive names like checkoutPage and receiptPopup make accidental selection easier to spot.
3. Capture a popup opened by a click
Register the popup wait before the click that triggers it. Then wait for the popup’s relevant readiness condition and capture the popup object:
import { test, expect } from '@playwright/test';
test('captures the report popup', async ({ page }) => {
await page.goto('https://example.com/dashboard');
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open report' }).click();
const reportPage = await popupPromise;
await reportPage.waitForLoadState('domcontentloaded');
await expect(reportPage).toHaveURL(/\/report/);
await expect(reportPage.getByRole('heading', { name: 'Report' })).toBeVisible();
await reportPage.screenshot({ path: 'report.png' });
});
Waiting for domcontentloaded only waits for the document to be parsed. If the application renders important content later, assert that content or wait for a specific locator before capture. Use load or networkidle only when those states fit the app; pages with ongoing network activity may never become idle.
You can also listen for the context’s page event when a new page may open without a known initiating page. That event includes popups, and the page can still be loading when the event fires:
const newPagePromise = context.waitForEvent('page');
// Trigger the action that opens a tab or popup here.
const newPage = await newPagePromise;
await newPage.waitForLoadState('domcontentloaded');
await newPage.screenshot({ path: 'new-page.png' });
4. Find the intended page among existing tabs
When several pages already exist, inspect the context’s pages and select by a stable property such as a known URL or expected content. Do not assume the last array entry is the right one unless your test controls and checks that ordering.
const pages = context.pages();
const targetPage = pages.find(page => page.url().includes('/reports/monthly'));
if (!targetPage) {
throw new Error(`Monthly report page not found. Open pages: ${pages.map(p => p.url()).join(', ')}`);
}
await targetPage.getByRole('heading', { name: 'Monthly report' }).waitFor();
await targetPage.screenshot({ path: 'monthly-report.png' });
URLs can be temporary, redirected, or shared by multiple app states. If URL alone is not unique, check a distinctive heading, landmark, or other user-visible content with a locator. Prefer accessible locators such as role and name when they identify the target reliably.
5. Confirm whether page selection is actually wrong
- Log the selected page’s
url()immediately before the screenshot. - Assert one or more pieces of expected content on that same page.
- Confirm whether the intended target is the original page, a popup, or another page in the context.
- Check that navigation and client-side rendering have reached the state the test needs.
- Only after those checks pass, investigate screenshot scope and rendering differences.
console.log('Capturing:', targetPage.url());
await targetPage.getByRole('heading', { name: 'Monthly report' }).waitFor();
await targetPage.screenshot({ path: 'monthly-report.png', fullPage: true });
fullPage: true changes the captured area to include the full page; it does not change which tab is captured. Without it, Playwright captures the viewport. If the page object and content are correct but pixels still differ, compare browser engine and version, operating system, browser settings, hardware, power source, and headless mode. Those environmental factors can affect rendering.
6. Screenshot options that affect the result
Page selection is controlled by the Page object. Screenshot options control what is captured or how the image is saved. Common options include:
| Option | Effect | When to use it |
|---|---|---|
path |
Writes the screenshot to a file. | Use a distinct path per page or test to avoid overwriting another capture. |
fullPage |
Captures the full scrollable page rather than just the viewport. | Use for long-page review; this does not select a different tab. |
type |
Selects PNG or JPEG output. | Choose the format your downstream workflow expects. |
quality |
Sets JPEG quality, where supported; it is not used for PNG. | Trade image size for JPEG fidelity. |
omitBackground |
Allows a transparent background where supported. | Useful for transparent page output. |
clip |
Captures a specified rectangle. | Use when a fixed region of the selected page is required. |
animations |
Controls finite and infinite animations during capture. | Disable or fast-forward animation when stable visual output matters. |
caret |
Controls whether the text caret is hidden during capture. | Hide it for less noisy snapshots. |
scale |
Controls output scaling as CSS pixels or device pixels. | Use CSS scaling to reduce large image output when appropriate. |
Consult the current Page screenshot API for the complete option list and supported values for your installed Playwright version. Keep screenshot configuration separate from page-selection logic so a wrong viewport or clipping rectangle is not mistaken for a wrong tab.
7. Troubleshooting wrong-tab screenshots
| Symptom | Likely cause | Fix |
|---|---|---|
| The original page is captured after clicking a link. | The link opened a popup, but the code still calls page.screenshot(). |
Await page.waitForEvent('popup') before the click, then call screenshot() on the returned page. |
| The capture changes between runs. | A shared variable was reassigned, or code relies on incidental page ordering. | Use separate named page references and select existing pages by a checked URL or expected content. |
| The right URL shows the wrong application state. | Navigation completed but client-side rendering or data loading has not. | Wait for a locator that represents the required state; assert it before capture. |
| The popup event times out. | The action did not open a popup, the listener was registered too late, or the target opened in the same tab. | Register the wait first. Verify the link behavior and choose navigation handling if the page stays in the same tab. |
| The image shows only part of a long page. | Default screenshot scope is the viewport. | Pass fullPage: true if the full scrollable page is required. |
| The screenshot looks different on another machine. | Browser version, OS, headless mode, settings, or other rendering conditions differ. | Compare execution environments and pin browser/runtime versions for visual comparisons. |
| The test hangs waiting for readiness. | networkidle is unsuitable for a page with persistent requests, or the expected selector never appears. |
Wait for a specific, bounded application condition and inspect the page URL, console, and network failures. |
8. Performance, reliability, and cost
Each additional page increases the work your test must manage, so close pages and contexts when finished. Reuse a context when pages need shared cookies or storage; use a separate context when isolation is required. Full-page screenshots and high device scale factors can produce larger images and take longer than viewport captures. Use the smallest capture scope and output format that meet the test’s needs.
For reliable captures, wait on observable application state rather than arbitrary long sleeps. A fixed delay can waste time on fast runs and still fail on slow ones. Keep the browser version and relevant environment consistent when comparing snapshots. If a capture fails, preserve the page URL and test logs so you can distinguish a selection mistake from failed navigation or rendering.
Playwright is an open-source browser automation library; this workflow does not require a screenshot API. Your costs depend on the infrastructure where you run browser tests, such as local machines or CI workers. ScreenshotNeo is a hosted API option when you want to capture a URL without managing browser setup; its plans and billing behavior are described below.
9. Or skip the browser setup
If your goal is a screenshot of a URL rather than controlling a Playwright page, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. The API accepts the URL directly, so there is no browser context or tab reference to manage.
See the ScreenshotNeo API documentation for parameters and response details. This cURL example saves a WebP capture of the report page:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/reports/monthly \
-o report.webp
Cookie banners, newsletter popups, and chat widgets are removed before capture, and each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. The MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is available on every plan, and yearly billing gives two months free.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
10. FAQ
Does Playwright screenshot whichever tab is active?
No. It captures the page object on which you call screenshot(). The browser’s visible foreground tab does not determine the target.
Do I need to call bringToFront() first?
No. Bringing a page to the front is not required for ordinary Playwright interactions or screenshots.
How do I capture a popup instead of the original page?
Wait for the popup event before the action that opens it, then call screenshot() on the returned popup page after its required content is ready.
Can a full-page screenshot fix a wrong-tab capture?
No. fullPage changes capture height, not page identity. Select the correct Page object first.
What if the target page has the right URL but still looks wrong?
Check the application’s visible state and readiness, then compare browser and host environment if the mismatch is visual.


