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

In Playwright, a new browser tab is a Page in the same BrowserContext. Start waiting for the tab before the click or other action that opens it, await the resulting page, wait for the navigation state you need, and read its URL with newPage.url() in JavaScript or new_page.url in Python.

const popupPromise = page.waitForEvent('popup');
await page.getByText('open new tab').click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
console.log(popup.url());

The reliable sequence

  1. Create the event wait before triggering the new tab.
  2. Perform the click, keyboard action, or script that opens it.
  3. Await the Page returned by the event.
  4. Wait for the navigation milestone that makes the URL meaningful.
  5. Read the URL from that page object.

Waiting first is essential. If the tab opens between the action and the listener registration, the event can be missed and the test may hang or time out.

When the current page opens the tab: popup

Use the page-level popup event when an action on a particular page creates the new tab, such as a link with target="_blank" or a button that calls window.open().

JavaScript

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

test('reads the URL of a popup', async ({ page }) => {
  await page.goto('https://example.com');

  const popupPromise = page.waitForEvent('popup');
  await page.getByRole('link', { name: 'Open new tab' }).click();

  const popup = await popupPromise;
  await popup.waitForLoadState('domcontentloaded');

  const url = popup.url();
  console.log(url);
  expect(url).toContain('example.com');
});

TypeScript with a typed page

const popupPromise: Promise<Page> = page.waitForEvent('popup');
await page.getByText('open new tab').click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
const url: string = popup.url();

The popup event is available after the new page has navigated to its initial URL. If redirects or a later script navigation determine the URL you actually need, add a URL wait or another targeted load wait before reading it.

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.

Python async API

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context()
        page = await context.new_page()
        await page.goto('https://example.com')

        async with page.expect_popup() as popup_info:
            await page.get_by_role('link', name='Open new tab').click()
        popup = await popup_info.value
        await popup.wait_for_load_state('domcontentloaded')

        url = popup.url
        print(url)
        await browser.close()

asyncio.run(main())

expect_popup() is the Python context-manager equivalent of registering waitForEvent('popup') before the action.

When any page in the context may open

A browser context can contain several pages and multiple pages can create tabs. Use the context-level page event when you want the next page created anywhere in that context rather than a popup tied to one opener.

const newPagePromise = context.waitForEvent('page');
await page.getByText('open new tab').click();
const newPage = await newPagePromise;
await newPage.waitForLoadState('domcontentloaded');
console.log(newPage.url());

The context event also fires for popup pages. Its broader scope is useful for downloads, authentication flows, or code that does not expose which existing page initiated the new tab. The trade-off is that unrelated page creation can satisfy the wait, so use the page-level event when you can identify the opener.

Getting the final URL after redirects

url() returns the page’s current URL at the moment you call it. A redirect chain may still be running after the popup event is delivered. Wait for the expected destination when the final address matters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Continue' }).click();
const popup = await popupPromise;

await popup.waitForURL('**/dashboard');
const finalUrl = popup.url();

You can use a string, glob, or predicate appropriate to your test. If the destination is known only by a query value, a predicate makes the assertion explicit:

await popup.waitForURL(url => url.origin === 'https://app.example.com'
  && url.pathname === '/dashboard'
  && url.searchParams.has('session'));

For a less specific readiness check, domcontentloaded is usually a useful boundary. Avoid using networkidle as a generic test condition; pages with analytics, sockets, or long polling may never become idle. Prefer a web assertion that proves the destination is ready.

Choosing the right event

Situation Wait for URL read
An action on one page opens its popup page.waitForEvent('popup') / page.expect_popup() popup.url() / popup.url
Any new tab in the browser context context.waitForEvent('page') newPage.url()
The address is changed by redirects or later navigation One of the waits above, then waitForURL() Read only after the final wait
Pages already existed before the action Prefer an event; use context.pages() only for an intentional snapshot Find the specific page, then read its URL

Why a new-tab URL is empty, wrong, or missed

The listener was registered too late

Symptom: the test waits until timeout. Fix: assign the promise or enter expect_popup() before clicking. Do not put the wait after the click.

You listened on the wrong object

Symptom: a context-level wait never resolves, or a page-level wait captures nothing. Fix: use popup for a popup initiated by the current page; use the context’s page event for any newly created page.

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

You read the initial URL instead of the destination

Symptom: the value is an intermediate login, tracking, or redirect address. Fix: call waitForURL() with the destination pattern or predicate, then read url().

The action did not create a page

Symptom: the wait times out because the site opened a same-tab navigation, blocked a popup, or required a user gesture. Fix: verify the behavior in headed mode, check popup-blocking conditions, and decide whether you should wait for page.waitForURL() on the existing page instead.

Several tabs open at once

Symptom: the first new page is not the one your assertion expects. Fix: listen on the known opener, serialize actions, or inspect the new page’s URL and content before selecting it. A broad context listener cannot infer your intent.

The page closes early

Symptom: URL access fails because the popup is immediately closed. Fix: read or assert the URL as soon as the required navigation completes, and investigate whether the site uses a short-lived intermediary window.

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

Practical patterns for robust tests

Keep the page reference

Return the popup from a helper so callers cannot accidentally inspect the original tab:

async function openTab(page) {
  const popupPromise = page.waitForEvent('popup');
  await page.getByRole('link', { name: 'Open new tab' }).click();
  const popup = await popupPromise;
  await popup.waitForLoadState('domcontentloaded');
  return popup;
}

const tab = await openTab(page);
await tab.waitForURL('**/receipt/*');
const receiptUrl = tab.url();

Use a bounded timeout

Set a timeout that reflects the application’s normal navigation time and keep the failure actionable. A timeout should identify whether the event, the load state, or the URL assertion failed; avoid masking the original problem with an unbounded wait.

Close pages you no longer need

In long-running suites, close the popup after extracting the URL when it is not needed for further assertions. This prevents accidental interactions with stale tabs and keeps the context manageable.

Performance and reliability considerations

  • Event waits are synchronized with page creation and avoid polling the page list.
  • domcontentloaded generally completes earlier than a full load while still providing a navigated document; choose the state that matches your assertion.
  • URL checks alone do not prove that application data rendered. Combine the final URL wait with a locator assertion when page readiness matters.
  • Use one browser context per isolated test scenario when unrelated tabs could otherwise satisfy a context-level event.
  • Redirects, authentication, consent screens, and client-side routing can all change the address after the initial popup event; model those transitions explicitly.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean image or PDF of a destination rather than browser automation, ScreenshotNeo provides a single request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. 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.

cURL

See the ScreenshotNeo API documentation for all options.

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

Python

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)

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}`);

Every plan includes the feature set: full-page and element captures, device and viewport controls, retina scale, PDF settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agent, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage data, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work.

The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create your free ScreenshotNeo account.

FAQ

Is a new tab a separate browser context?

No. It is a new Page inside the same BrowserContext, sharing that context’s cookies and other browser state.

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

Should I use context.pages() instead of an event?

Only when you deliberately want a snapshot of all existing pages. An event wait associates one newly created page with the action that triggered it and is safer for tests.

Can I read the URL without waiting for a load state?

You can read the initial URL as soon as the popup event resolves, but wait for a targeted navigation or URL condition when redirects or client-side navigation determine the value you need.

Frequently Asked Questions

Does Playwright support headless mode for popup URL checks?

Yes. The popup and page events work the same way in headless and headed Chromium, Firefox, and WebKit runs; headless mode changes visibility, not the Page-based URL API.

What if the link opens a tab only with a modifier key?

Perform the exact gesture that creates the tab while the event wait is active, for example a mouse click with the required button or keyboard modifiers, then await the resulting Page.

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.

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.