Skip to content

Commit

Permalink
fix(role selector): expanded=false does not match elements without ar…
Browse files Browse the repository at this point in the history
…ia-expanded
  • Loading branch information
dgozman committed Nov 19, 2022
1 parent 3ed9dab commit 3e1d219
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 21 deletions.
3 changes: 2 additions & 1 deletion docs/src/selectors.md
Expand Up @@ -765,9 +765,10 @@ Attributes supported by the role selector:
Note that unlike most other attributes, `disabled` is inherited through the DOM hierarchy.
Learn more about [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).

* `expanded` - a boolean attribute that is usually set by `aria-expanded`. Examples:
* `expanded` - an attribute that is usually set by `aria-expanded`. Available values for expanded are `true`, `false` and `"none"`. Examples:
- `role=button[expanded=true]`, equivalent to `role=button[expanded]`
- `role=button[expanded=false]`
- `role=button[expanded="none"]`, meaning that `aria-expanded` is not present

Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).

Expand Down
Expand Up @@ -70,8 +70,13 @@ function validateAttributes(attrs: AttributeSelectorPart[], role: string) {
}
case 'expanded': {
validateSupportedRole(attr.name, kAriaExpandedRoles, role);
validateSupportedValues(attr, [true, false]);
validateSupportedValues(attr, [true, false, 'none']);
validateSupportedOp(attr, ['<truthy>', '=']);
if (attr.op === '<truthy>') {
// Do not match "none" in "treeitem[expanded]".
attr.op = '=';
attr.value = true;
}
break;
}
case 'level': {
Expand Down
14 changes: 10 additions & 4 deletions packages/playwright-core/src/server/injected/roleUtils.ts
Expand Up @@ -670,14 +670,20 @@ export function getAriaPressed(element: Element): boolean | 'mixed' {
}

export const kAriaExpandedRoles = ['application', 'button', 'checkbox', 'combobox', 'gridcell', 'link', 'listbox', 'menuitem', 'row', 'rowheader', 'tab', 'treeitem', 'columnheader', 'menuitemcheckbox', 'menuitemradio', 'rowheader', 'switch'];
export function getAriaExpanded(element: Element): boolean {
export function getAriaExpanded(element: Element): boolean | 'none' {
// https://www.w3.org/TR/wai-aria-1.2/#aria-expanded
// https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
if (element.tagName === 'DETAILS')
return (element as HTMLDetailsElement).open;
if (kAriaExpandedRoles.includes(getAriaRole(element) || ''))
return getAriaBoolean(element.getAttribute('aria-expanded')) === true;
return false;
if (kAriaExpandedRoles.includes(getAriaRole(element) || '')) {
const expanded = element.getAttribute('aria-expanded');
if (expanded === null)
return 'none';
if (expanded === 'true')
return true;
return false;
}
return 'none';
}

export const kAriaLevelRoles = ['heading', 'listitem', 'row', 'treeitem'];
Expand Down
48 changes: 33 additions & 15 deletions tests/page/selectors-role.spec.ts
Expand Up @@ -169,26 +169,41 @@ test('should support pressed', async ({ page }) => {

test('should support expanded', async ({ page }) => {
await page.setContent(`
<button>Hi</button>
<button aria-expanded="true">Hello</button>
<button aria-expanded="false">Bye</button>
<div role="treeitem">Hi</div>
<div role="treeitem" aria-expanded="true">Hello</div>
<div role="treeitem" aria-expanded="false">Bye</div>
`);
expect(await page.locator(`role=button[expanded]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-expanded="true">Hello</button>`,

expect(await page.locator('role=treeitem').evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem">Hi</div>`,
`<div role="treeitem" aria-expanded="true">Hello</div>`,
`<div role="treeitem" aria-expanded="false">Bye</div>`,
]);
expect(await page.locator(`role=button[expanded=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-expanded="true">Hello</button>`,
expect(await page.getByRole('treeitem').evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem">Hi</div>`,
`<div role="treeitem" aria-expanded="true">Hello</div>`,
`<div role="treeitem" aria-expanded="false">Bye</div>`,
]);
expect(await page.getByRole('button', { expanded: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-expanded="true">Hello</button>`,

expect(await page.locator(`role=treeitem[expanded]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem" aria-expanded="true">Hello</div>`,
]);
expect(await page.locator(`role=button[expanded=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-expanded="false">Bye</button>`,
expect(await page.locator(`role=treeitem[expanded=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem" aria-expanded="true">Hello</div>`,
]);
expect(await page.getByRole('button', { expanded: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-expanded="false">Bye</button>`,
expect(await page.getByRole('treeitem', { expanded: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem" aria-expanded="true">Hello</div>`,
]);

expect(await page.locator(`role=treeitem[expanded=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem" aria-expanded="false">Bye</div>`,
]);
expect(await page.getByRole('treeitem', { expanded: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem" aria-expanded="false">Bye</div>`,
]);

expect(await page.locator(`role=treeitem[expanded="none"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="treeitem">Hi</div>`,
]);
});

Expand Down Expand Up @@ -403,4 +418,7 @@ test('errors', async ({ page }) => {

const e7 = await page.$('role=button[name]').catch(e => e);
expect(e7.message).toContain(`"name" attribute must have a value`);

const e8 = await page.$('role=treeitem[expanded="bar"]').catch(e => e);
expect(e8.message).toContain(`"expanded" must be one of true, false, "none"`);
});

0 comments on commit 3e1d219

Please sign in to comment.