Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
“Execution context was destroyed, most likely because of a navigation” means the document changed while Playwright was evaluating JavaScript or using a reference tied to the old document. Synchronize the action with the expected destination using page.waitForURL(), then query the new page. If the URL stays the same, wait for the specific UI state, assertion, or response that proves the operation finished.
What the error means
Each loaded document has a JavaScript execution context. A full navigation replaces that document and destroys its context. An in-flight page.evaluate(), an evaluation-created object, or an ElementHandle can therefore be interrupted even though a later page.url() check has not changed yet.
Playwright issue #27374 describes this timing: evaluation fails because navigation has already destroyed the context, while URL inspection can still be premature. Another report, #27406, shows the error after a logout click redirected to another domain in Playwright 1.38.1 with Chromium on macOS 13.5.2. That reproduction illustrates the mechanism; it does not establish behavior for every browser or Playwright version.
Recommended Free Tools
Fix a click or submit that should navigate
Register the URL wait and perform the triggering action together. This prevents a gap in which navigation can start before the wait is listening:
#1 Best Overall
await Promise.all([
page.waitForURL('**/dashboard'),
page.getByRole('link', { name: 'Dashboard' }).click(),
]);
const title = await page.title();
Replace the pattern and locator with the destination and control in your test. Playwright documents page.waitForURL() for a matching main-frame URL and recommends an explicit URL when an action can trigger multiple navigations; see the Navigations guide and Page API reference.
Form submission example
await Promise.all([
page.waitForURL('**/account'),
page.getByRole('button', { name: 'Sign in' }).click(),
]);
await expect(page.getByRole('heading', { name: 'Account' })).toBeVisible();
The URL wait confirms the navigation target. The assertion confirms that the new document contains the page your test actually needs.
When the URL does not change
A URL wait cannot prove that a same-URL single-page application update completed. Wait for the observable result instead:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Rank #2
await page.getByRole('button', { name: 'Refresh results' }).click();
await expect(page.getByRole('status')).toHaveText('Updated');
Choose a signal that represents the behavior under test:
- UI state: a locator becomes visible, enabled, or populated.
- Text or accessibility state: a web assertion matches the expected message or status.
- Network operation: wait for the specific response when the request itself is what matters.
Interactions auto-wait for target elements to become actionable, but that does not tell Playwright when your application’s asynchronous rendering is complete. Use web assertions as described in the navigation guidance.
Choosing the right Playwright wait
| What must be true | Use | What it proves |
|---|---|---|
| A known destination was reached | page.waitForURL(pattern) |
The main-frame URL matches the pattern. |
| A same-URL update finished | Locator or web assertion | The relevant element, text, or state is ready. |
| A tested request completed | Response wait | The specified network response occurred. |
| A browser lifecycle milestone matters | domcontentloaded, load, or commit |
That milestone occurred, not that later application data is ready. |
| All network activity became quiet | Do not use as a general readiness test | Playwright marks networkidle discouraged for tests; assertions are preferred. |
As Playwright’s Navigations guide puts it, “There is no way to tell that the page is loaded, it depends on the page, framework, etc.” A browser load event can occur before client-rendered data or controls appear.
Rank #3
Reacquire elements after navigation
Do not carry an ElementHandle or an object returned from evaluation across a document replacement. Those references belong to the previous page context. Use a locator after the navigation:
await Promise.all([
page.waitForURL('**/settings'),
page.getByRole('link', { name: 'Settings' }).click(),
]);
const email = await page.getByLabel('Email').inputValue();
Locators describe how to find an element in the current page state and can resolve it again after navigation. Handles refer to particular page objects. Playwright’s guidance on evaluating JavaScript, locators, and handles explains these distinctions.
Why common “fixes” fail
Replacing synchronization with a sleep
page.waitForTimeout() waits a fixed duration but does not establish whether navigation happened or whether the required UI state is true. It can be too short on a slow run and wasteful on a fast one.
Using networkidle everywhere
Background polling, analytics, service workers, or long-lived connections can prevent a quiet network. The Page API discourages networkidle for tests and recommends web assertions instead.
Continuing with waitForNavigation()
page.waitForNavigation() is deprecated. The Page API states: “This method is inherently racy, please use page.waitForURL() instead.”
Checking the URL only after catching the error
A catch block can run in the narrow interval after the old context was destroyed but before the URL property reflects the new destination. Synchronize before the action rather than retrying the same evaluation blindly.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.A practical diagnosis checklist
- Identify the operation immediately before the failure: click, submit, logout, reload, redirect, or script-triggered navigation.
- Decide whether a full navigation is expected or whether the application should remain on the same URL.
- For a known destination, pair the action with
page.waitForURL()inPromise.all. - For a same-URL change, assert the resulting locator, text, state, or specific response.
- After a document replacement, reacquire locators and avoid old handles.
- Only use a lifecycle wait when that exact milestone is the behavior being tested.
Or skip the browser setup: ScreenshotNeo
If your goal is simply to capture a page rather than test a navigation workflow, ScreenshotNeo returns a screenshot or PDF through one GET request, so there is no Playwright context to synchronize. It accepts consent banners before capture and bills only clean shots; failed loads, bot checks, blank pages, timeouts, and cache hits are not billed, with the result identified by response headers. Its MCP server also exposes screenshot, page-info, and PDF tools to AI clients.
For the API parameters and the complete option list, see the ScreenshotNeo documentation. Example:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Bottom line for your test
Make the expected transition explicit. Pair a navigation-triggering action with page.waitForURL(); for same-URL behavior, assert the concrete application state; then find elements again in the new document. This removes the race that destroys the old execution context instead of masking it with a delay or a generic network wait.
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.

