{"record":{"id":"98ad4be35c426931","repo":"microsoft/playwright","slug":"clicking-the-checkbox-did-not-change-its-state","errorCode":null,"errorMessage":"Clicking the checkbox did not change its state","messagePattern":"Clicking the checkbox did not change its state","errorType":"exception","errorClass":"NonRecoverableDOMError","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/dom.ts","lineNumber":844,"sourceCode":"      const result = await progress.race(this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {}));\n      if (result === 'error:notconnected' || result.received === 'error:notconnected')\n        throwElementIsNotAttached();\n      return { matches: result.matches, isRadio: result.isRadio };\n    };\n    await this._markAsTargetElement(progress);\n    const checkedState = await isChecked(progress);\n    if (checkedState.matches === state)\n      return 'done';\n    if (!state && checkedState.isRadio)\n      throw new NonRecoverableDOMError('Cannot uncheck radio button. Radio buttons can only be unchecked by selecting another radio button in the same group.');\n    const result = await this._click(progress, { ...options, waitAfter: 'disabled' });\n    if (result !== 'done')\n      return result;\n    if (options.trial)\n      return 'done';\n    const finalState = await isChecked(progress);\n    if (finalState.matches !== state)\n      throw new NonRecoverableDOMError('Clicking the checkbox did not change its state');\n    return 'done';\n  }\n\n  async boundingBox(progress: Progress): Promise<types.Rect | null> {\n    return await progress.race(this._page.delegate.getBoundingBox(this));\n  }\n\n  async screenshot(progress: Progress, options: ScreenshotOptions): Promise<Buffer> {\n    return await this._page.screenshotter.screenshotElement(progress, this, options);\n  }\n\n  async querySelector(progress: Progress, selector: string, options: types.StrictOptions): Promise<ElementHandle | null> {\n    return progress.race(this._querySelector(selector, options));\n  }\n\n  private async _querySelector(selector: string, options: types.StrictOptions): Promise<ElementHandle | null> {\n    return this._frame.selectors.query(selector, options, this);\n  }","sourceCodeStart":826,"sourceCodeEnd":862,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/dom.ts#L826-L862","documentation":"After _setChecked performs a click to toggle a checkbox, it re-reads the state; if the state did not flip to the requested value it throws NonRecoverableDOMError('Clicking the checkbox did not change its state'). This indicates the click landed but did not toggle — typically because a custom widget, an intercepting label, or a JS event handler prevented the native change.","triggerScenarios":"Calling check()/uncheck() on a custom checkbox widget that toggles via JS rather than the native click→checked flip; a label/div overlapping the input so the click does not reach the real checkbox; the element looks like a checkbox but is a styled div.","commonSituations":"Component libraries (e.g. Material/Chakra) that render a hidden native input plus a visible div and toggle checked in a click handler that the synthetic click sequence does not trigger identically; double-click handlers that flip state twice back to the original.","solutions":["Verify the target is a real <input type=checkbox>; if it is a custom widget, click the underlying input or use a role-based locator that resolves to it.","Ensure no overlay intercepts the click (remove force, let auto-wait clear overlays).","If a JS handler must run, click the element that the app's own toggle binds to (often a label).","As a last resort, set the state via page.evaluate and dispatch the change event the app expects."],"exampleFix":"// before\nawait page.locator('.fancy-checkbox').check(); // click doesn't flip → throws\n// after — target the real input\nawait page.locator('input[type=checkbox]').check();","handlingStrategy":"validation","validationCode":"// Target the real native checkbox the app toggles.\nconst isNative = await page.locator('input[type=checkbox]').count() > 0;\nif (isNative) await page.locator('input[type=checkbox]').check();","typeGuard":"// Resolve to a native checkbox rather than a styled wrapper.\nasync function isNativeCheckbox(locator: Locator): Promise<boolean> {\n  return await locator.evaluate(el => el.tagName === 'INPUT' && (el as HTMLInputElement).type === 'checkbox');\n}","tryCatchPattern":null,"preventionTips":["Locate the underlying <input type=checkbox>, not its visual wrapper div.","Remove force so auto-wait clears any intercepting overlay before the toggle click.","If the widget toggles via JS, click the element the app binds its handler to."],"tags":["form","checkbox","state","custom-widget"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}