{"record":{"id":"3686667c3d6a2fca","repo":"grafana/k6","slug":"checking-element-is-enabled-w","errorCode":null,"errorMessage":"checking element is enabled: %w","messagePattern":"checking element is enabled: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/element_handle.go","lineNumber":1091,"sourceCode":"}\n\n// IsEditable checks if the element is editable.\nfunc (h *ElementHandle) IsEditable() (bool, error) {\n\tok, err := h.isEditable(h.ctx, 0)\n\t// We don't care anout timeout errors here!\n\tif err != nil && !errors.Is(err, ErrTimedOut) {\n\t\treturn false, fmt.Errorf(\"checking element is editable: %w\", err)\n\t}\n\n\treturn ok, nil\n}\n\n// IsEnabled checks if the element is enabled.\nfunc (h *ElementHandle) IsEnabled() (bool, error) {\n\tok, err := h.isEnabled(h.ctx, 0)\n\t// We don't care anout timeout errors here!\n\tif err != nil && !errors.Is(err, ErrTimedOut) {\n\t\treturn false, fmt.Errorf(\"checking element is enabled: %w\", err)\n\t}\n\n\treturn ok, nil\n}\n\n// IsHidden checks if the element is hidden.\nfunc (h *ElementHandle) IsHidden() (bool, error) {\n\tok, err := h.isHidden(h.ctx)\n\t// We don't care anout timeout errors here!\n\tif err != nil && !errors.Is(err, ErrTimedOut) {\n\t\treturn false, fmt.Errorf(\"checking element is hidden: %w\", err)\n\t}\n\n\treturn ok, nil\n}\n\n// IsVisible checks if the element is visible.\nfunc (h *ElementHandle) IsVisible() (bool, error) {","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/grafana/k6/blob/3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee/internal/js/modules/k6/browser/common/element_handle.go#L1073-L1109","documentation":"ElementHandle.isEnabled() runs waitForElementState(['enabled']) with a zero timeout and by design ignores ErrTimedOut. The wrapped error is therefore not 'the element is disabled' (that just returns false) but a hard failure: a detached handle, an execution context destroyed by navigation, or a closed CDP session.","triggerScenarios":"The button/control node was replaced by an SPA re-render after the handle was taken; the page navigated away; the containing frame was closed; the browser was shutting down when the check ran.","commonSituations":"Buttons recreated after async state updates (e.g., submit buttons swapped for spinners); cached handles reused after checkout/step transitions; checks racing logout or page close.","solutions":["Re-query with a live locator: await page.locator(sel).isEnabled()","Re-acquire the element handle immediately before the check","Wrap in try/catch, treating an error as 'element no longer present'","Avoid caching handles across navigations, waits, and multi-step flows"],"exampleFix":"// before\nconst el = await page.$('#submit');\nawait page.waitForNavigation();\nconst enabled = await el.isEnabled(); // throws: old page's handle\n\n// after\nawait page.waitForNavigation();\nconst enabled = await page.locator('#submit').isEnabled();","handlingStrategy":"try-catch","validationCode":"// Check enabled state through a locator that re-resolves the node\nconst enabled = await page.locator('#submit').isEnabled();","typeGuard":null,"tryCatchPattern":"let enabled = false;\ntry {\n  enabled = await el.isEnabled();\n} catch (e) {\n  if (/not attached|Execution context|Could not find object/i.test(String(e.message))) {\n    enabled = await page.locator(sel).isEnabled();\n  } else throw e;\n}","preventionTips":["Use page.locator(sel).isEnabled() instead of cached element handles","Re-acquire handles after waits that can trigger re-renders or navigations","Only transport errors (stale handle, dead context) throw here - disabled elements return false"],"tags":["browser","element-state","enabled","stale-element","k6"],"backgroundTag":"stale-element-reference","analyzedSha":"3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}