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

Most page.waitForEvent() failures come from arming the wait too late, listening on the wrong object or event, rejecting the event with a predicate, or allowing the page or browser context to close. Create the wait promise first, perform the action without awaiting the event, and only then await the promise:

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

If that pattern is already correct, inspect the event scope, trigger, predicate, timeout category, page lifecycle and any dialog handler that could be blocking the action.

What page.waitForEvent() does

page.waitForEvent() waits for a named event emitted by a Playwright Page and resolves with that event’s data. You can provide a predicate to accept only matching event data and a timeout for how long Playwright should wait. See the Page API reference.

The event must actually be emitted by the object you are observing. A page’s popup event concerns a popup opened by that page; a browser context’s page event covers new pages created anywhere in that context. The wrong source or event name leaves a valid promise waiting for something that will never arrive.

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.

1. Arm the wait before the action

Do not await the event before running the action that should produce it. This serializes the test in the wrong order and can cause a timeout:

// Wrong: the click never runs until a popup already exists
const popup = await page.waitForEvent('popup');
await page.getByRole('button', { name: 'Open window' }).click();

Create the promise without awaiting it, trigger the behavior, then await the result:

const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Open window' }).click();
const popup = await popupPromise;
await popup.waitForLoadState();

The same ordering applies to downloads:

const downloadPromise = page.waitForEvent('download');
await page.getByText('Download file').click();
const download = await downloadPromise;
await download.saveAs('artifacts/report.pdf');

Playwright documents this pre-action pattern in its Pages guide and Downloads guide.

2. Verify the event, source and trigger

Choose the event that matches the behavior

  • Use page.waitForEvent('popup') for a popup associated with the current page.
  • Use context.waitForEvent('page') when any new page in the browser context may be created.
  • Use page.waitForEvent('download') for a download attachment.

A popup event is not necessarily available at the instant application code calls window.open. The Page API describes it as becoming available after navigation to the initial URL reaches the point where its network response starts loading. If you need to observe the request itself, use context routing or request events rather than treating popup as a request notification.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const contextPagePromise = context.waitForEvent('page');
await page.getByRole('link', { name: 'Open in new tab' }).click();
const newPage = await contextPagePromise;

Check that the control really performs the expected operation. A single-page application may update the current page instead of opening a popup, a download may be prevented by permissions, and application code may emit a custom event unrelated to Playwright’s built-in page events.

Check the object lifecycle

A pending page event wait throws if the page closes before the event fires. A context wait similarly fails if the browser context closes. Keep the page or context alive through the action and wait, and investigate code that calls page.close(), context.close() or exits a fixture early.

const popupPromise = page.waitForEvent('popup');
await triggerPopup();
const popup = await popupPromise;
// Do not close the context until popup work is complete
await popup.waitForLoadState();

See the BrowserContext API for context event behavior and closure semantics.

3. Inspect predicates and timeout settings

Predicates can reject the event you need

With a predicate, Playwright keeps waiting until the predicate returns true. Log or simplify the predicate to confirm that the event data has the property and value you expect:

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.
const popupPromise = page.waitForEvent('popup', popup =>
  popup.url().includes('/account')
);
await page.getByRole('button', { name: 'Account' }).click();
const accountPage = await popupPromise;

If the popup initially has an intermediate URL, the predicate may reject it indefinitely. First wait for the event, then wait for navigation or inspect the final URL:

const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Account' }).click();
const popup = await popupPromise;
await popup.waitForLoadState();
await popup.waitForURL('**/account');

Identify the timeout that actually failed

An event-wait timeout is different from a test, assertion, action, navigation, fixture or global timeout. Playwright Test configures these scopes separately; changing the wrong setting can hide the real problem. Read the error text and call log before editing configuration. The Timeouts guide lists the categories.

Set a longer event timeout only when the correct event is known to arrive after a legitimate delay:

const popupPromise = page.waitForEvent('popup', { timeout: 30_000 });
await page.getByRole('button', { name: 'Open report' }).click();
const popup = await popupPromise;

Increasing a timeout cannot fix a wrong event name, wrong source object, a predicate that never accepts, an action that emits no event, or a page that closes early. A zero timeout may disable the wait timeout in APIs that support it, so use that only with an intentional external deadline.

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

4. Look for dialogs that stall the triggering action

JavaScript alert, confirm, prompt and beforeunload dialogs can block an action. If no dialog listener is installed, Playwright automatically dismisses dialogs. Once you register page.on('dialog') or a context dialog handler, your handler must call accept() or dismiss():

page.on('dialog', async dialog => {
  console.log(dialog.type(), dialog.message());
  await dialog.accept();
});

const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Continue' }).click();
const popup = await popupPromise;

A handler that only logs the dialog and never resolves it leaves the page blocked, making the associated click appear to hang. The Dialogs guide explains the automatic-dismissal and handler rules.

5. Separate actionability failures from event failures

Locator actions perform actionability checks: the locator must resolve appropriately, be visible, stable, able to receive pointer events and enabled. If those checks do not pass before the action timeout, the click fails; no event wait can succeed because the trigger never happened.

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

When this fails, inspect the call log for the click’s visibility, overlay, uniqueness or enabled-state error. The Auto-waiting guide describes these checks. Do not mistake an actionability TimeoutError for an event timeout.

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

Failure symptoms and the next check

Symptom Inspect Next step
Event wait times out Event name, source object, trigger, predicate and wait timeout Arm the correct wait before the trigger; remove or correct the predicate.
Error says page or context closed Fixture and application lifecycle Keep the object open through the wait or fix the flow that closes it.
Click or other action hangs Dialog handler and action call log Accept or dismiss registered dialogs, then resolve actionability errors.
Test reports a broad timeout Test, assertion, action, navigation, fixture or global scope Identify the reported timeout class before changing configuration.

Reusable diagnostic checklist

  1. Confirm the action is executed after the event promise is created.
  2. Confirm the action can produce the named event in this application state.
  3. Confirm the wait is attached to the page or context that emits the event.
  4. Temporarily remove the predicate and log the returned event object.
  5. Check whether a popup needs navigation or URL waiting after the event.
  6. Check page and context closure in fixtures, cleanup hooks and error paths.
  7. Inspect registered dialog handlers and resolve every dialog.
  8. Read the action call log to distinguish actionability from event waiting.
  9. Change a timeout only after the preceding checks establish that the event is valid but slow.

Or skip the browser setup

If your goal is to capture a page after debugging or to add screenshots to a workflow, ScreenshotNeo provides a single HTTP request instead of maintaining Playwright capture code. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets before the capture; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers.

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

See the ScreenshotNeo documentation for parameters and response details. The same request in 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)

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

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for the free plan.

FAQ

Why does a popup wait resolve late?

The popup event becomes available after its initial navigation reaches the documented loading point. Wait for the popup first, then use waitForLoadState() or waitForURL() for later navigation.

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

Should I replace waitForEvent with a fixed sleep?

No. A sleep does not verify that the intended event occurred and creates race conditions. Correct the event ordering, scope, trigger and predicate instead.

What if the application opens a tab outside my page?

Listen for page on the owning BrowserContext, then identify the new page, rather than waiting for a popup event on an unrelated page.

Frequently Asked Questions

Can I wait for multiple possible events?

Create waits for the specific events you can legitimately receive and use a controlled race with cleanup; do not hide an incorrect event source behind an arbitrary delay.

How do I prove the action ran?

Use the Playwright call log, add a temporary log immediately before the action, and verify the locator’s actionability error or success before diagnosing the event promise.

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.