diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index fb16c64ea3..8bea09ade0 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -29,7 +29,7 @@ import type { CSSComplexSelectorList } from '../isomorphic/cssParser'; import { generateSelector } from './selectorGenerator'; import type * as channels from '@protocol/channels'; import { Highlight } from './highlight'; -import { getAriaCheckedStrict, getAriaDisabled, getAriaLabelledByElements, getAriaRole, getElementAccessibleName } from './roleUtils'; +import { getChecked, getAriaDisabled, getAriaLabelledByElements, getAriaRole, getElementAccessibleName } from './roleUtils'; import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils'; import { asLocator } from '../isomorphic/locatorGenerators'; import type { Language } from '../isomorphic/locatorGenerators'; @@ -614,7 +614,7 @@ export class InjectedScript { if (state === 'checked' || state === 'unchecked') { const need = state === 'checked'; - const checked = getAriaCheckedStrict(element); + const checked = getChecked(element, false); if (checked === 'error') throw this.createStacklessError('Not a checkbox or radio button'); return need === checked; diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts index c3c5eecb9c..53455be8c4 100644 --- a/packages/playwright-core/src/server/injected/roleUtils.ts +++ b/packages/playwright-core/src/server/injected/roleUtils.ts @@ -662,13 +662,13 @@ export function getAriaSelected(element: Element): boolean { export const kAriaCheckedRoles = ['checkbox', 'menuitemcheckbox', 'option', 'radio', 'switch', 'menuitemradio', 'treeitem']; export function getAriaChecked(element: Element): boolean | 'mixed' { - const result = getAriaCheckedStrict(element); + const result = getChecked(element, true); return result === 'error' ? false : result; } -export function getAriaCheckedStrict(element: Element): boolean | 'mixed' | 'error' { +export function getChecked(element: Element, allowMixed: boolean): boolean | 'mixed' | 'error' { // https://www.w3.org/TR/wai-aria-1.2/#aria-checked // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings - if (element.tagName === 'INPUT' && (element as HTMLInputElement).indeterminate) + if (allowMixed && element.tagName === 'INPUT' && (element as HTMLInputElement).indeterminate) return 'mixed'; if (element.tagName === 'INPUT' && ['checkbox', 'radio'].includes((element as HTMLInputElement).type)) return (element as HTMLInputElement).checked; @@ -676,7 +676,7 @@ export function getAriaCheckedStrict(element: Element): boolean | 'mixed' | 'err const checked = element.getAttribute('aria-checked'); if (checked === 'true') return true; - if (checked === 'mixed') + if (allowMixed && checked === 'mixed') return 'mixed'; return false; } diff --git a/tests/page/locator-convenience.spec.ts b/tests/page/locator-convenience.spec.ts index de4b9f90e6..fa58badac8 100644 --- a/tests/page/locator-convenience.spec.ts +++ b/tests/page/locator-convenience.spec.ts @@ -144,6 +144,21 @@ it('isChecked should work', async ({ page }) => { expect(error.message).toContain('Not a checkbox or radio button'); }); +it('isChecked should work for indeterminate input', async ({ page }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/20190' }); + + await page.setContent(``); + await page.locator('input').evaluate((e: HTMLInputElement) => e.indeterminate = true); + + expect(await page.locator('input').isChecked()).toBe(true); + await expect(page.locator('input')).toBeChecked(); + + await page.locator('input').uncheck(); + + expect(await page.locator('input').isChecked()).toBe(false); + await expect(page.locator('input')).not.toBeChecked(); +}); + it('allTextContents should work', async ({ page }) => { await page.setContent(`