{"record":{"id":"1890df444785c064","repo":"grafana/k6","slug":"expected-node-but-got-s","errorCode":null,"errorMessage":"expected node but got %s","messagePattern":"expected node but got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/element_handle.go","lineNumber":1881,"sourceCode":"\tcase error:\n\t\tif e == nil {\n\t\t\treturn errors.New(\"DOM error is nil\")\n\t\t}\n\t\terr, serr = e, e.Error()\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected DOM error type %T\", v)\n\t}\n\tvar uerr *k6ext.UserFriendlyError\n\tif errors.As(err, &uerr) {\n\t\treturn err\n\t}\n\tif strings.Contains(serr, \"timed out\") {\n\t\treturn &k6ext.UserFriendlyError{\n\t\t\tErr: ErrTimedOut,\n\t\t}\n\t}\n\tif s := \"error:expectednode:\"; strings.HasPrefix(serr, s) {\n\t\treturn fmt.Errorf(\"expected node but got %s\", strings.TrimPrefix(serr, s))\n\t}\n\n\tif serr == \"error:notconnected\" {\n\t\treturn ErrElementNotAttachedToDOM\n\t}\n\n\tif errors.Is(err, ErrElementNotVisible) {\n\t\treturn ErrElementNotVisible\n\t}\n\n\tif errors.Is(err, ErrElementNotAttachedToDOM) {\n\t\treturn ErrElementNotAttachedToDOM\n\t}\n\n\terrs := map[string]string{\n\t\t\"error:notelement\":             \"node is not an element\",\n\t\t\"error:nothtmlelement\":         \"not an HTMLElement\",\n\t\t\"error:notfillableelement\":     \"element is not an <input>, <textarea> or [contenteditable] element\",","sourceCodeStart":1863,"sourceCodeEnd":1899,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/element_handle.go#L1863-L1899","documentation":"This error is a translation of a raw Chromium DevTools Protocol error whose message starts with 'error:expectednode:'. CDP returns that error when an internal command (DOM.requestNode, DOM.describeNode, DOM.resolveNode) is given a remote object reference that is not a DOM node. k6 rewrites it to 'expected node but got <type>' where <type> is the actual remote object type Chromium reported (e.g. 'object', 'string', 'function'). It almost always means the ElementHandle being used no longer refers to a live DOM node.","triggerScenarios":"Calling ElementHandle methods that internally resolve or adopt the handle as a node (boundingBox, contentFrame, the cross-context adoption in waitForSelector, clicks on stale handles) after the page navigated or the node was replaced; using a plain JSHandle (a non-element object) where an ElementHandle is required; Chrome disposing remote objects under heavy load.","commonSituations":"Reusing an ElementHandle obtained before a page.goto or an SPA route change; React/Vue re-rendering a node between query and action; scripts running many VUs against a memory-starved Chrome where remote objects get garbage collected; confusing page.evaluateHandle results (JS values) with element handles.","solutions":["Re-acquire the element immediately before acting on it (fresh page.$ / waitForSelector) instead of reusing a cached handle","Wrap the action in a bounded retry that re-queries the selector when this message appears","Prefer selector-based auto-waiting APIs (page.click(selector)) over manual handle plumbing","If it appears intermittently under load, give Chrome more CPU/memory or lower the VU count per browser"],"exampleFix":"// before\nconst btn = await page.$('#submit');\nawait page.goto('https://example.com/next');\nawait btn.click(); // stale handle\n\n// after\nawait page.goto('https://example.com/next');\nconst btn = await page.$('#submit');\nawait btn.click();","handlingStrategy":"retry","validationCode":"// verify the handle still points at a live, visible node before acting\nif (!el) throw new Error('element handle is null');\nif (!(await el.isVisible())) throw new Error('element no longer visible');","typeGuard":"function isElementHandleLike(v) {\n  return !!v && typeof v.click === 'function' && typeof v.boundingBox === 'function' && typeof v.screenshot === 'function';\n}","tryCatchPattern":"try {\n  await el.click();\n} catch (e) {\n  if (/expected node but got/.test(e.message)) {\n    el = await page.waitForSelector(sel, { state: 'visible' }); // re-acquire, retry once\n    await el.click();\n  } else throw e;\n}","preventionTips":["Never cache ElementHandles across navigations or SPA route changes","Prefer selector-based APIs (page.click('#id')) over handle-based ones","Re-query elements after goto, waitForNavigation, or SPA transitions","Keep Chrome load reasonable; stale remote objects appear more often on overloaded browsers"],"tags":["browser","cdp","element-handle","stale-handle","navigation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}