Skip to content
Puppeteer · error message

Puppeteer: Execution context was destroyed, most likely because of a navigation

RetryableNavigation & network

Last verified

The exact message
Error: Execution context was destroyed, most likely because of a navigation.

What it means

Puppeteer runs your page.evaluate(), page.$eval(), page.$$eval() and element-handle methods inside the JavaScript execution context of the current document. When the page navigates or reloads, Chrome destroys that context and creates a new one. Any call still running in, or bound to, the old context fails with this message, because the code and objects it relied on no longer exist.

It nearly always means two things happened at once: your script was evaluating or reading the DOM while the page was leaving. Typical triggers are a click that submits a form followed immediately by an evaluate, a site that redirects shortly after load, or a loop that clicks a link on each iteration and then reads data before the new page has arrived.

What is not wrong: the evaluated function itself. Run it again once the new document is loaded and it usually succeeds.

What it looks like

// Collect URLs first, then visit them; no stale contexts
const links = await page.$$eval('a.product', as => as.map(a => a.href));
for (const url of links) {
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  const title = await page.$eval('h1', el => el.textContent.trim());
  console.log(title);
}

Why it happens

  • Reading the DOM immediately after a click that navigates, without waiting for the navigation.
  • The site performs a JavaScript or meta-refresh redirect after the initial load.
  • A missing await lets page.goto() and page.evaluate() run concurrently.
  • Reusing ElementHandles or JSHandles obtained before the navigation.
  • Iterating over links and navigating inside the loop while still holding the previous page's handles.

How to fix it

  1. Wrap navigating actions with Promise.all([page.waitForNavigation(), page.click(selector)]) before evaluating.
  2. After redirects, wait for an element that only exists on the final page with page.waitForSelector().
  3. Collect plain data (hrefs as strings) first, then navigate to each with page.goto() instead of clicking stored handles.
  4. Add the missing await so navigation and evaluation cannot overlap.
  5. For unpredictable redirect chains, retry the evaluate once on this specific error.

How to stop it recurring

Treat every action that can navigate as a point where all handles become invalid. Extract plain values (strings, numbers) from the page rather than carrying handles across steps, and pair navigation triggers with an explicit wait for the destination. Locator-based actions re-query the page and are less exposed to this race.

Official documentation: pptr.dev/api/puppeteer.page.waitfornavigation. See all Puppeteer error messages.

Related errors

Definitions

Quick answers

What does "Execution context was destroyed, most likely because of a navigation" mean in Puppeteer?

Puppeteer runs your page.evaluate() , page.$eval() , page.$$eval() and element-handle methods inside the JavaScript execution context of the current document. When the page navigates or reloads, Chrome destroys that context and creates a new one. Any call still running in, or bound to, the old context fails with this message, because the code and objects it relied on no longer exist.

How do I fix "Execution context was destroyed, most likely because of a navigation"?

1. Wrap navigating actions with Promise.all([page.waitForNavigation(), page.click(selector)]) before evaluating. 2. After redirects, wait for an element that only exists on the final page with page.waitForSelector(). 3. Collect plain data (hrefs as strings) first, then navigate to each with page.goto() instead of clicking stored handles. 4. Add the missing await so navigation and evaluation cannot overlap. 5. For unpredictable redirect chains, retry the evaluate once on this specific error.

Run browser automation you can watch and take over

Autonoly runs each job in a real cloud Chromium browser driven by Playwright. Timeouts and detached elements still happen; the difference is that you can watch the run live, see the step that failed, and take control of the browser to finish it.

Also see Live browser control.