fix(web): a native checkbox, radio, or option reads its own state over a stale aria attribute

Merged
OK
1mo ago
+95-4
  • Repro: <input type="checkbox" checked aria-checked="false">. The reader took aria-checked after the native read and let it override, so the node read checked: false, isChecked() answered false, and toBeChecked failed on a box the browser would submit as checked. The inverse markup (unchecked input, aria-checked="true") made toBeChecked pass. Same for <option selected aria-selected="false">. The agent's observation showed the same wrong state.
  • Playwright's getChecked and getAriaSelected (roleUtils.ts) read .checked for an input checkbox or radio and .selected for an option, and consult ARIA only for roles without native state. The reader now does the same: ARIA fills in only when the element has no native checked or selected state, so div[role=checkbox], role=switch, and role=tab keep reading their attribute.
  • check() and uncheck() never used this reader for their skip decision, so only the read and assert layer changes.
if (checked === null) {
  const ariaChecked = el.getAttribute('aria-checked');
  if (ariaChecked !== null) checked = ariaChecked === 'true';
}

packages/web/src/in-page/read-semantics.ts:758. The new browser test packages/web/tests/integration/read-node-checked.test.ts compares every control's read against Playwright's isChecked().


Devin Review