{"record":{"id":"7ecbd806a5c88616","repo":"grafana/k6","slug":"describing-dom-node-w","errorCode":null,"errorMessage":"describing DOM node: %w","messagePattern":"describing DOM node: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/execution_context.go","lineNumber":149,"sourceCode":"\te.logger.Debugf(\n\t\t\"ExecutionContext:adoptElementHandle\",\n\t\t\"sid:%s stid:%s fid:%s ectxid:%d furl:%q ehtid:%s ehsid:%s\",\n\t\te.sid, e.stid, e.fid, e.id, e.furl,\n\t\tefid, esid)\n\n\tif eh.execCtx == e {\n\t\treturn nil, errors.New(\"already belongs to the same execution context\")\n\t}\n\tif e.frame == nil {\n\t\treturn nil, errors.New(\"does not have a frame owner\")\n\t}\n\n\tvar node *cdp.Node\n\tvar err error\n\n\taction := dom.DescribeNode().WithObjectID(eh.remoteObject.ObjectID)\n\tif node, err = action.Do(cdp.WithExecutor(e.ctx, e.session)); err != nil {\n\t\treturn nil, fmt.Errorf(\"describing DOM node: %w\", err)\n\t}\n\n\treturn e.adoptBackendNodeID(node.BackendNodeID)\n}\n\n// eval evaluates the provided JavaScript within this execution context and\n// returns a value or handle.\n//\n//nolint:funlen\nfunc (e *ExecutionContext) eval(\n\tapiCtx context.Context, opts evalOptions, js string, args ...any,\n) (any, error) {\n\tif escapesSobekValues(args...) {\n\t\treturn nil, errors.New(\"sobek.Value escaped\")\n\t}\n\te.logger.Debugf(\n\t\t\"ExecutionContext:eval\",\n\t\t\"sid:%s stid:%s fid:%s ectxid:%d furl:%q %s\",","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/execution_context.go#L131-L167","documentation":"During adoptElementHandle, k6 calls DOM.describeNode with the source handle's objectID to learn its backend node ID before adopting it into another execution context. The CDP call fails when the remote object behind the handle was already disposed — its objectID is invalid because the owning context was destroyed (navigation), the object was garbage collected, or the session closed.","triggerScenarios":"waitForSelector finds an element in the utility world and adopts it into the main world while a navigation destroys contexts; cross-frame handle adoption after the source frame navigated; holding a handle long enough that Chrome garbage collects the object.","commonSituations":"Element queries racing page transitions in SPAs; frames/elements replaced between query and adoption inside k6's own waitForSelector path; long-running iterations keeping handles alive past navigations.","solutions":["Retry at the user level: re-run the query+action pair — adoption races are transient","Wait for the page/frame to be idle (waitForLoadState) before querying elements","Shorten handle lifetime: query, act, discard","If persistent, check for repeated navigations (redirect loops) on the page"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  el = await page.waitForSelector('#x');\n} catch (e) {\n  if (/describing DOM node|adopting element handle/.test(e.message)) {\n    await page.waitForLoadState();\n    el = await page.waitForSelector('#x');\n  } else throw e;\n}","preventionTips":["Do not cache handles across navigation boundaries","Let auto-waiting APIs do query+action in one step","Stabilize the page (waitForLoadState) before querying","Watch for redirect chains that destroy contexts repeatedly"],"tags":["browser","cdp","navigation","race-condition","element-handle"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}