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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use page.waitForURL() to wait until the main frame navigates to a URL that matches your expected destination. When a click or other action may navigate quickly, start waiting before triggering it—usually with Promise.all(). For a child frame, use frame.waitForURL(); for a test assertion about the final address, use expect(page).toHaveURL().

Wait for a page URL after an action

page.waitForURL() waits for the main frame to navigate to a matching URL. The matcher can be a string, regular expression, URLPattern, or predicate. With a plain string and no wildcards, Playwright matches the URL exactly.

For example, if clicking an account link should take the browser to one known address:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.getByRole('link', { name: 'Account' }).click();
await page.waitForURL('https://example.com/account');

This sequential form is readable, but if the navigation might finish before the second line begins waiting, register the wait first. The following pattern starts the wait and click together:

await Promise.all([
  page.waitForURL('**/dashboard'),
  page.getByRole('button', { name: 'Continue' }).click(),
]);

Use the concurrent form for a click, form submission, or other action that can cause a quick navigation. The URL matcher should describe the destination you actually expect, not just any navigation. Playwright’s navigation guide recommends explicitly waiting for a specific URL when an interaction can trigger multiple navigations.

Choose a matcher for the destination

A URL matcher should be as narrow as the application allows while still accommodating legitimate variation. Pick based on which URL parts are stable.

Exact string for a fixed address

A string without wildcards is an exact match. Use it when scheme, host, path, and any included query or fragment are known and expected to remain fixed:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.waitForURL('https://example.com/account');

If the real destination has a query string or fragment, include it when exact matching that full address is what the test needs. If some portion is intentionally variable, use a more suitable matcher rather than weakening the expectation without reason.

Glob for a variable path or host

A glob is useful when one or more portions can vary but a recognizable URL shape remains. For example, this waits for a URL ending in a login route:

await page.waitForURL('**/login');

Be aware that a broad glob can match more destinations than intended. If the site has multiple hosts or routes ending in the same path, constrain the pattern further or use a predicate.

Regular expression for structured variation

Use a regular expression when the changing part follows a pattern, such as a numeric order ID:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.waitForURL(//orders/d+$/);

This expression matches a URL whose path ends with /orders/ followed by one or more digits. Anchoring with $ prevents a longer trailing path from being accepted.

Predicate for query parameters or URL logic

A predicate receives a URL object. It is often the clearest choice when the path must be exact but query parameters can appear in any order or have additional values:

await page.waitForURL(url =>
  url.pathname === '/search' && url.searchParams.has('q')
);

This waits for the /search path and requires a q parameter without requiring a particular parameter order or query value. If the value matters, compare it explicitly—for example, url.searchParams.get('q') === 'playwright'.

URLPattern when it fits your URL rules

URLPattern is also a supported matcher. It can express URL patterns using a URL-specific pattern object. Choose it when that syntax makes the expected host, path, or other URL components easier for your team to read; otherwise, a glob, regular expression, or predicate may be simpler. Keep the matching rule focused on the parts that define the expected destination.

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.

Set the lifecycle signal when needed

URL matching answers where the main frame navigated. It does not by itself prove that the page’s application content is ready for the next assertion. waitForURL() also accepts lifecycle options: load, domcontentloaded, networkidle, and commit.

For example, to require the destination URL at the document’s DOM content-loaded point:

await page.waitForURL('**/dashboard', { waitUntil: 'domcontentloaded' });

The API documentation discourages using networkidle for tests: it defines that state as having no network connections for at least 500 ms and recommends relying on web assertions to assess readiness instead. A page can continue background requests after its useful content is ready, or display content before network activity becomes idle. Prefer an assertion about the UI that matters, such as a visible heading or loaded result, rather than treating network idleness as a universal readiness signal.

Use the right tool: wait, assert, or target a frame

Navigation synchronization: page.waitForURL()

Use this when the test must coordinate an action with a main-frame URL transition. It is a wait for navigation identity, not a substitute for checking the rendered state.

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

URL assertion: expect(page).toHaveURL()

When the test’s purpose is to assert the final address, use Playwright’s web assertion:

await expect(page).toHaveURL(//dashboard$/);

toHaveURL() accepts exact, regular-expression, URLPattern, or predicate matching. In a test, this assertion expresses the expected outcome directly and participates in Playwright’s web assertion behavior. You may still need a separate locator assertion to establish that the page content is ready and correct.

Child-frame navigation: frame.waitForURL()

page.waitForURL() is for the main frame. If the expected navigation happens inside an iframe, call waitForURL() on the corresponding Frame instead:

await frame.waitForURL('**/embedded/complete');

Make sure frame refers to the child frame whose URL is changing. Waiting on the page while only an embedded frame navigates watches the wrong browsing context.

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

Why not use waitForNavigation()?

page.waitForNavigation() is deprecated in the Page API and documented as inherently racy; Playwright directs users to page.waitForURL() instead. URL-based waiting is more explicit because it states which destination should satisfy the wait, rather than merely waiting for a navigation event that could lead somewhere unexpected.

For the same reason, avoid using a generic navigation wait as a proxy for the test’s real outcome. If a control might cause more than one navigation, match the intended URL. If the requirement is that a particular interface element appears, assert that element after the relevant transition.

Complete TypeScript example

This example uses Playwright’s test runner, clicks a link, waits concurrently for the destination, then checks both the URL and a meaningful page element:

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

test('account link opens the account page', async ({ page }) => {
  await page.goto('https://example.com');

  await Promise.all([
    page.waitForURL('https://example.com/account'),
    page.getByRole('link', { name: 'Account' }).click(),
  ]);

  await expect(page).toHaveURL('https://example.com/account');
  await expect(page.getByRole('heading', { name: 'Account' })).toBeVisible();
});

Replace the example host, link name, and heading with values from the application under test. The two assertions serve different purposes: the URL assertion verifies the destination, while the heading assertion verifies a visible page outcome. If a destination path contains a dynamic identifier, change the matcher to an appropriate glob, regular expression, URLPattern, or predicate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting URL waits

The wait times out

  • The expected URL is wrong. Inspect the actual destination, including the host, path, query string, and fragment. Adjust the matcher only for variation the application genuinely allows.
  • The wait started too late. If navigation can happen quickly, register page.waitForURL() before the click or other triggering action using Promise.all().
  • The action did not navigate. A single-page application may update content without changing the address, or the click may not have activated the intended control. Check the actual behavior and test the relevant UI state if no URL change is expected.
  • The wrong frame is being watched. Use the page for a main-frame transition and the relevant Frame for a child-frame transition.

The wait passes on an unintended URL

The pattern is too broad. A glob such as **/login can accept multiple hosts and routes with that ending. Narrow the glob, anchor a regular expression, compare the full address, or use a predicate that checks the required pathname and query values.

The URL matches but the page is not ready

A matching address is not an assertion about rendered content. Follow the URL wait with a locator assertion for the element or state the test actually needs. Avoid relying on networkidle as a general readiness check; Playwright discourages it for tests.

The test uses waitForNavigation()

Replace the navigation wait with a URL-specific page.waitForURL() and state the expected destination. If the intent is only to verify an address as a test outcome, use expect(page).toHaveURL().

Or skip the browser setup

If your goal is to capture a screenshot of a URL—not to synchronize a Playwright test with a navigation—ScreenshotNeo can return a screenshot or PDF through one GET request. It does not replace page.waitForURL() when your test needs to wait for its own browser navigation.

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

For example, this cURL request captures the supplied URL as a WebP image; see the ScreenshotNeo API documentation for request options and response details:

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

ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client.

The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for ScreenshotNeo’s free plan.

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.

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.