mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
fix(check): support all ARIA roles that could be aria-checked (#18304)
Fixes #18193.
This commit is contained in:
parent
329b3eadb4
commit
3cd64e1449
@ -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 { getAriaDisabled, getAriaRole, getElementAccessibleName } from './roleUtils';
|
||||
import { getAriaCheckedStrict, getAriaDisabled, getAriaRole, getElementAccessibleName } from './roleUtils';
|
||||
import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
|
||||
import { asLocator } from '../isomorphic/locatorGenerators';
|
||||
import type { Language } from '../isomorphic/locatorGenerators';
|
||||
@ -609,16 +609,11 @@ export class InjectedScript {
|
||||
return !disabled && editable;
|
||||
|
||||
if (state === 'checked' || state === 'unchecked') {
|
||||
if (['checkbox', 'radio'].includes(element.getAttribute('role') || '')) {
|
||||
const result = element.getAttribute('aria-checked') === 'true';
|
||||
return state === 'checked' ? result : !result;
|
||||
}
|
||||
if (element.nodeName !== 'INPUT')
|
||||
const need = state === 'checked';
|
||||
const checked = getAriaCheckedStrict(element);
|
||||
if (checked === 'error')
|
||||
throw this.createStacklessError('Not a checkbox or radio button');
|
||||
if (!['radio', 'checkbox'].includes((element as HTMLInputElement).type.toLowerCase()))
|
||||
throw this.createStacklessError('Not a checkbox or radio button');
|
||||
const result = (element as HTMLInputElement).checked;
|
||||
return state === 'checked' ? result : !result;
|
||||
return need === checked;
|
||||
}
|
||||
throw this.createStacklessError(`Unexpected element state "${state}"`);
|
||||
}
|
||||
|
@ -635,6 +635,10 @@ 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);
|
||||
return result === 'error' ? false : result;
|
||||
}
|
||||
export function getAriaCheckedStrict(element: Element): 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)
|
||||
@ -647,8 +651,9 @@ export function getAriaChecked(element: Element): boolean | 'mixed' {
|
||||
return true;
|
||||
if (checked === 'mixed')
|
||||
return 'mixed';
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return 'error';
|
||||
}
|
||||
|
||||
export const kAriaPressedRoles = ['button'];
|
||||
|
@ -76,6 +76,16 @@ test.describe('toBeChecked', () => {
|
||||
expect(error.message).toContain(`expect.toBeChecked with timeout 1000ms`);
|
||||
expect(error.message).toContain('waiting for "locator(\'input2\')"');
|
||||
});
|
||||
|
||||
test('with role', async ({ page }) => {
|
||||
for (const role of ['checkbox', 'menuitemcheckbox', 'option', 'radio', 'switch', 'menuitemradio', 'treeitem']) {
|
||||
await test.step(`role=${role}`, async () => {
|
||||
await page.setContent(`<div role=${role} aria-checked=true>I am checked</div>`);
|
||||
const locator = page.locator('div');
|
||||
await expect(locator).toBeChecked();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('toBeEditable', () => {
|
||||
|
@ -69,21 +69,29 @@ it('should uncheck radio by aria role', async ({ page }) => {
|
||||
});
|
||||
|
||||
it('should check the box by aria role', async ({ page }) => {
|
||||
await page.setContent(`<div role='checkbox' id='checkbox'>CHECKBOX</div>
|
||||
<script>
|
||||
checkbox.addEventListener('click', () => checkbox.setAttribute('aria-checked', 'true'));
|
||||
</script>`);
|
||||
await page.check('div');
|
||||
expect(await page.evaluate(() => window['checkbox'].getAttribute('aria-checked'))).toBe('true');
|
||||
for (const role of ['checkbox', 'menuitemcheckbox', 'option', 'radio', 'switch', 'menuitemradio', 'treeitem']) {
|
||||
await it.step(`role=${role}`, async () => {
|
||||
await page.setContent(`<div role='${role}' id='checkbox'>CHECKBOX</div>
|
||||
<script>
|
||||
checkbox.addEventListener('click', () => checkbox.setAttribute('aria-checked', 'true'));
|
||||
</script>`);
|
||||
await page.check('div');
|
||||
expect(await page.evaluate(() => window['checkbox'].getAttribute('aria-checked'))).toBe('true');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should uncheck the box by aria role', async ({ page }) => {
|
||||
await page.setContent(`<div role='checkbox' id='checkbox' aria-checked="true">CHECKBOX</div>
|
||||
<script>
|
||||
checkbox.addEventListener('click', () => checkbox.setAttribute('aria-checked', 'false'));
|
||||
</script>`);
|
||||
await page.uncheck('div');
|
||||
expect(await page.evaluate(() => window['checkbox'].getAttribute('aria-checked'))).toBe('false');
|
||||
for (const role of ['checkbox', 'menuitemcheckbox', 'option', 'radio', 'switch', 'menuitemradio', 'treeitem']) {
|
||||
await it.step(`role=${role}`, async () => {
|
||||
await page.setContent(`<div role='${role}' id='checkbox' aria-checked="true">CHECKBOX</div>
|
||||
<script>
|
||||
checkbox.addEventListener('click', () => checkbox.setAttribute('aria-checked', 'false'));
|
||||
</script>`);
|
||||
await page.uncheck('div');
|
||||
expect(await page.evaluate(() => window['checkbox'].getAttribute('aria-checked'))).toBe('false');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw when not a checkbox', async ({ page }) => {
|
||||
@ -92,6 +100,12 @@ it('should throw when not a checkbox', async ({ page }) => {
|
||||
expect(error.message).toContain('Not a checkbox or radio button');
|
||||
});
|
||||
|
||||
it('should throw when not a checkbox 2', async ({ page }) => {
|
||||
await page.setContent(`<div role=button>Check me</div>`);
|
||||
const error = await page.check('div').catch(e => e);
|
||||
expect(error.message).toContain('Not a checkbox or radio button');
|
||||
});
|
||||
|
||||
it('should check the box inside a button', async ({ page }) => {
|
||||
await page.setContent(`<div role='button'><input type='checkbox'></div>`);
|
||||
await page.check('input');
|
||||
|
Loading…
Reference in New Issue
Block a user