How to fix "Execution context was destroyed, most likely because of a navigation"?
PlaywrightThe message "execution context was destroyed, most likely because of a navigation" means Playwright started evaluating JavaScript in a document that was replaced before the evaluation completed. It appears most often as page.evaluate: execution context was destroyed, most likely because of a navigation when a click triggers a full-page navigation and your script tries to read from the old document immediately after. The same error shows up when using stale ElementHandle references or page.$eval() calls that span a navigation boundary.
Common mistake
await page.getByRole('link', { name: 'Next page' }).click();
// The click triggered a navigation — the old context is already gone
const title = await page.evaluate(() => document.title);
The click() returns before the navigation completes, so the evaluate call lands in a torn-down document.
The fix
Wrap the click and navigation wait together so Playwright knows to hold for the new document before evaluating:
import { test, expect } from '@playwright/test';
test('reads title after navigation', async ({ page }) => {
await page.goto('https://example.com/articles');
await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.getByRole('link', { name: 'Next page' }).click(),
]);
// Safe — new document is fully committed
const title = await page.evaluate(() => document.title);
expect(title).toContain('Page 2');
});
The modern Playwright equivalent using waitForURL is often cleaner:
await page.getByRole('link', { name: 'Next page' }).click();
await page.waitForURL('**/articles?page=2');
// Locator-based assertions auto-retry in the new document
await expect(page.getByRole('heading', { name: /Page 2/ })).toBeVisible();
For SPAs that update the URL without a full reload, waitForURL is usually sufficient. For multi-page apps, waitForNavigation with commit or domcontentloaded is more reliable than load.
Why it works
When a navigation occurs, Chromium destroys the V8 execution context for the departing document. Any pending evaluate call that was holding a reference to that context receives the "execution context was destroyed" error. Using Promise.all with waitForNavigation ensures your script only proceeds after Playwright has attached to the new document's context. Locator-based assertions avoid the problem entirely because they re-query the DOM on each retry in the current document.
Tips
- Prefer locator assertions (expect(locator).toBeVisible()) over page.evaluate() where possible — they are context-safe across navigations.
- Avoid storing ElementHandle references across navigation boundaries; always re-query after the new page has loaded.
- If evaluation must happen immediately after a navigation-triggering action, waitForURL is simpler than waitForNavigation for most cases and accepts URL patterns or predicates.
- Related error with a similar root cause: element not attached to the DOM often follows the same navigation race pattern but surfaces at the locator action level.