Puppeteer: Node is detached from document
Last verified
Error: Node is detached from documentWhat it means
You called click(), hover(), scrollIntoView() or a similar method on an ElementHandle whose DOM node has been removed from the page. A handle refers to one specific node; if the page re-renders that part of the DOM, the replacement looks the same but is a different node, and the old handle is orphaned. This is Puppeteer's version of a stale element reference.
It is common when scraping lists with pagination or infinite scroll, and on React, Vue or Angular sites that re-render components after data loads. Code that collects handles with page.$$() and then clicks each one in a loop is especially prone to it, because the first click often re-renders the list.
What is not wrong: the selector still matches. Querying it again will usually find the new node immediately.
What it looks like
// Fragile
const buttons = await page.$$('.row button.expand');
for (const b of buttons) await b.click();
// Robust: re-query each time by index
const count = await page.$$eval('.row button.expand', els => els.length);
for (let i = 0; i < count; i++) {
await page.locator(`.row:nth-of-type(${i + 1}) button.expand`).click();
}Why it happens
- The framework re-rendered the component between querying the handle and using it.
- Looping over page.$$() results while each click changes the list.
- The handle was obtained before a navigation or a client-side route change.
- A modal or popup was closed and re-opened, recreating its contents.
How to fix it
- Query the element right before acting on it, inside the loop rather than before it.
- Use page.click(selector) or page.locator(selector).click(), which resolve the selector at action time.
- For lists, extract stable identifiers (text, href, data-id) first and locate each item fresh by that identifier.
- Wait for the re-render to settle, for example by waiting for a loading indicator to disappear, before acting.
How to stop it recurring
Keep element handles short-lived and prefer selector-based or Locator API calls, which never hold onto a specific node. When iterating, iterate over data, not over handles. After any action that triggers a re-render, wait for a visible signal that the UI has settled before the next interaction.
Official documentation: pptr.dev/api/puppeteer.elementhandle. See all Puppeteer error messages.
Related errors
- Execution context was destroyed, most likely because of a navigationError: Execution context was destroyed, most likely because of a navigation.
- Node is either not clickable or not an ElementError: Node is either not clickable or not an Element
- Attempted to use detached FrameError: Attempted to use detached Frame 'A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6'.
Definitions
- PuppeteerPuppeteer is Google's open-source Node.js library for controlling Chrome and Firefox. It drives Chrome over th
- Chrome DevTools Protocol (CDP)The Chrome DevTools Protocol (CDP) is the JSON-over-WebSocket interface Chrome exposes for inspecting and cont
- CSS SelectorA CSS selector is a pattern used to identify and target specific HTML elements on a web page, widely used in b
- Headless ChromeHeadless Chrome is Google Chrome running without a visible window. It renders pages and runs JavaScript like n
- IframeAn iframe (inline frame) is an HTML element that embeds a separate web page within a parent page, creating an
- Web ElementA web element is any individual component rendered on a web page — buttons, links, input fields, images, text
Quick answers
What does "Node is detached from document" mean in Puppeteer?
You called click() , hover() , scrollIntoView() or a similar method on an ElementHandle whose DOM node has been removed from the page. A handle refers to one specific node; if the page re-renders that part of the DOM, the replacement looks the same but is a different node, and the old handle is orphaned. This is Puppeteer's version of a stale element reference.
How do I fix "Node is detached from document"?
1. Query the element right before acting on it, inside the loop rather than before it. 2. Use page.click(selector) or page.locator(selector).click(), which resolve the selector at action time. 3. For lists, extract stable identifiers (text, href, data-id) first and locate each item fresh by that identifier. 4. Wait for the re-render to settle, for example by waiting for a loading indicator to disappear, before acting.
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.