Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →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.
#1 Best Overall
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.
Rank #2
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.
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.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Rank #4
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.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →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
- Confirm the action is executed after the event promise is created.
- Confirm the action can produce the named event in this application state.
- Confirm the wait is attached to the page or context that emits the event.
- Temporarily remove the predicate and log the returned event object.
- Check whether a popup needs navigation or URL waiting after the event.
- Check page and context closure in fixtures, cleanup hooks and error paths.
- Inspect registered dialog handlers and resolve every dialog.
- Read the action call log to distinguish actionability from event waiting.
- 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.
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.
Recommended Free Tools
Quick Recap
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.

