feat(locator): Add ariaChildren Option to Locator's getByRole Method.
This commit is contained in:
parent
275f334b58
commit
3772bcf79f
|
|
@ -1311,6 +1311,13 @@ Option that controls whether hidden elements are matched. By default, only non-h
|
||||||
|
|
||||||
Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
|
Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
|
||||||
|
|
||||||
|
## locator-get-by-role-option-ariaChildren
|
||||||
|
- `ariaChildren` <[boolean]>
|
||||||
|
|
||||||
|
Option that controls whether elements referenced by `aria-owns` and `aria-controls` are included in the search. By default, elements referenced by `aria-owns` and `aria-controls` are not included.
|
||||||
|
|
||||||
|
Learn more about [`aria-owns`](https://www.w3.org/TR/wai-aria-1.2/#aria-owns) and [`aria-controls`](https://www.w3.org/TR/wai-aria-1.2/#aria-controls).
|
||||||
|
|
||||||
## locator-get-by-role-option-level
|
## locator-get-by-role-option-level
|
||||||
* since: v1.27
|
* since: v1.27
|
||||||
- `level` <[int]>
|
- `level` <[int]>
|
||||||
|
|
@ -1354,6 +1361,7 @@ Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-sele
|
||||||
- %%-locator-get-by-role-option-disabled-%%
|
- %%-locator-get-by-role-option-disabled-%%
|
||||||
- %%-locator-get-by-role-option-expanded-%%
|
- %%-locator-get-by-role-option-expanded-%%
|
||||||
- %%-locator-get-by-role-option-includeHidden-%%
|
- %%-locator-get-by-role-option-includeHidden-%%
|
||||||
|
- %%-locator-get-by-role-option-ariaChildren-%%
|
||||||
- %%-locator-get-by-role-option-level-%%
|
- %%-locator-get-by-role-option-level-%%
|
||||||
- %%-locator-get-by-role-option-name-%%
|
- %%-locator-get-by-role-option-name-%%
|
||||||
- %%-locator-get-by-role-option-pressed-%%
|
- %%-locator-get-by-role-option-pressed-%%
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,10 @@ type RoleEngineOptions = {
|
||||||
level?: number;
|
level?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
includeHidden?: boolean;
|
includeHidden?: boolean;
|
||||||
|
ariaChildren?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const kSupportedAttributes = ['selected', 'checked', 'pressed', 'expanded', 'level', 'disabled', 'name', 'include-hidden'];
|
const kSupportedAttributes = ['selected', 'checked', 'pressed', 'expanded', 'level', 'disabled', 'name', 'include-hidden', 'aria-children'];
|
||||||
kSupportedAttributes.sort();
|
kSupportedAttributes.sort();
|
||||||
|
|
||||||
function validateSupportedRole(attr: string, roles: string[], role: string) {
|
function validateSupportedRole(attr: string, roles: string[], role: string) {
|
||||||
|
|
@ -116,6 +117,11 @@ function validateAttributes(attrs: AttributeSelectorPart[], role: string): RoleE
|
||||||
options.includeHidden = attr.op === '<truthy>' ? true : attr.value;
|
options.includeHidden = attr.op === '<truthy>' ? true : attr.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'aria-children': {
|
||||||
|
validateSupportedValues(attr, [true, false]);
|
||||||
|
options.ariaChildren = attr.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unknown attribute "${attr.name}", must be one of ${kSupportedAttributes.map(a => `"${a}"`).join(', ')}.`);
|
throw new Error(`Unknown attribute "${attr.name}", must be one of ${kSupportedAttributes.map(a => `"${a}"`).join(', ')}.`);
|
||||||
}
|
}
|
||||||
|
|
@ -124,9 +130,31 @@ function validateAttributes(attrs: AttributeSelectorPart[], role: string): RoleE
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAriaChildren(element: Element, scope: SelectorRoot): Element[] {
|
||||||
|
const documentRoot = scope.ownerDocument || scope;
|
||||||
|
const ariaElements: Element[] = [];
|
||||||
|
const ariaAttributes = ['aria-owns', 'aria-controls'];
|
||||||
|
|
||||||
|
ariaAttributes.forEach(attr => {
|
||||||
|
const ariaValue = element.getAttribute(attr);
|
||||||
|
if (!ariaValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ids = ariaValue.split(/\s+/);
|
||||||
|
for (const id of ids) {
|
||||||
|
const ownedElement = documentRoot.getElementById(id);
|
||||||
|
if (ownedElement)
|
||||||
|
ariaElements.push(ownedElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ariaElements;
|
||||||
|
}
|
||||||
|
|
||||||
function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: boolean): Element[] {
|
function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: boolean): Element[] {
|
||||||
const result: Element[] = [];
|
const result: Element[] = [];
|
||||||
const match = (element: Element) => {
|
const match = (element: Element) => {
|
||||||
|
|
||||||
if (getAriaRole(element) !== options.role)
|
if (getAriaRole(element) !== options.role)
|
||||||
return;
|
return;
|
||||||
if (options.selected !== undefined && getAriaSelected(element) !== options.selected)
|
if (options.selected !== undefined && getAriaSelected(element) !== options.selected)
|
||||||
|
|
@ -147,11 +175,9 @@ function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: bo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (options.name !== undefined) {
|
if (options.name !== undefined) {
|
||||||
// Always normalize whitespace in the accessible name.
|
|
||||||
const accessibleName = normalizeWhiteSpace(getElementAccessibleName(element, !!options.includeHidden));
|
const accessibleName = normalizeWhiteSpace(getElementAccessibleName(element, !!options.includeHidden));
|
||||||
if (typeof options.name === 'string')
|
if (typeof options.name === 'string')
|
||||||
options.name = normalizeWhiteSpace(options.name);
|
options.name = normalizeWhiteSpace(options.name);
|
||||||
// internal:role assumes that [name="foo"i] also means substring.
|
|
||||||
if (internal && !options.exact && options.nameOp === '=')
|
if (internal && !options.exact && options.nameOp === '=')
|
||||||
options.nameOp = '*=';
|
options.nameOp = '*=';
|
||||||
if (!matchesAttributePart(accessibleName, { name: '', jsonPath: [], op: options.nameOp || '=', value: options.name, caseSensitive: !!options.exact }))
|
if (!matchesAttributePart(accessibleName, { name: '', jsonPath: [], op: options.nameOp || '=', value: options.name, caseSensitive: !!options.exact }))
|
||||||
|
|
@ -170,8 +196,11 @@ function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: bo
|
||||||
shadows.push(element.shadowRoot);
|
shadows.push(element.shadowRoot);
|
||||||
}
|
}
|
||||||
shadows.forEach(query);
|
shadows.forEach(query);
|
||||||
|
if (options.ariaChildren && root instanceof Element) {
|
||||||
|
const ariaChildren = getAriaChildren(root, scope);
|
||||||
|
ariaChildren.forEach(match);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
query(scope);
|
query(scope);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export type ByRoleOptions = {
|
||||||
exact?: boolean;
|
exact?: boolean;
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
includeHidden?: boolean;
|
includeHidden?: boolean;
|
||||||
|
ariaChildren?: boolean;
|
||||||
level?: number;
|
level?: number;
|
||||||
name?: string | RegExp;
|
name?: string | RegExp;
|
||||||
pressed?: boolean;
|
pressed?: boolean;
|
||||||
|
|
@ -68,6 +69,8 @@ export function getByRoleSelector(role: string, options: ByRoleOptions = {}): st
|
||||||
props.push(['expanded', String(options.expanded)]);
|
props.push(['expanded', String(options.expanded)]);
|
||||||
if (options.includeHidden !== undefined)
|
if (options.includeHidden !== undefined)
|
||||||
props.push(['include-hidden', String(options.includeHidden)]);
|
props.push(['include-hidden', String(options.includeHidden)]);
|
||||||
|
if (options.ariaChildren !== undefined)
|
||||||
|
props.push(['aria-children', String(options.ariaChildren)]);
|
||||||
if (options.level !== undefined)
|
if (options.level !== undefined)
|
||||||
props.push(['level', String(options.level)]);
|
props.push(['level', String(options.level)]);
|
||||||
if (options.name !== undefined)
|
if (options.name !== undefined)
|
||||||
|
|
|
||||||
36
packages/playwright-core/types/types.d.ts
vendored
36
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -2915,6 +2915,15 @@ export interface Page {
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
||||||
|
/**
|
||||||
|
* Option that controls whether elements referenced by `aria-owns` and `aria-controls` are included in the search. By
|
||||||
|
* default, elements referenced by `aria-owns` and `aria-controls` are not included.
|
||||||
|
*
|
||||||
|
* Learn more about [`aria-owns`](https://www.w3.org/TR/wai-aria-1.2/#aria-owns) and
|
||||||
|
* [`aria-controls`](https://www.w3.org/TR/wai-aria-1.2/#aria-controls).
|
||||||
|
*/
|
||||||
|
ariaChildren?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
||||||
*
|
*
|
||||||
|
|
@ -6674,6 +6683,15 @@ export interface Frame {
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
||||||
|
/**
|
||||||
|
* Option that controls whether elements referenced by `aria-owns` and `aria-controls` are included in the search. By
|
||||||
|
* default, elements referenced by `aria-owns` and `aria-controls` are not included.
|
||||||
|
*
|
||||||
|
* Learn more about [`aria-owns`](https://www.w3.org/TR/wai-aria-1.2/#aria-owns) and
|
||||||
|
* [`aria-controls`](https://www.w3.org/TR/wai-aria-1.2/#aria-controls).
|
||||||
|
*/
|
||||||
|
ariaChildren?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
||||||
*
|
*
|
||||||
|
|
@ -13294,6 +13312,15 @@ export interface Locator {
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
||||||
|
/**
|
||||||
|
* Option that controls whether elements referenced by `aria-owns` and `aria-controls` are included in the search. By
|
||||||
|
* default, elements referenced by `aria-owns` and `aria-controls` are not included.
|
||||||
|
*
|
||||||
|
* Learn more about [`aria-owns`](https://www.w3.org/TR/wai-aria-1.2/#aria-owns) and
|
||||||
|
* [`aria-controls`](https://www.w3.org/TR/wai-aria-1.2/#aria-controls).
|
||||||
|
*/
|
||||||
|
ariaChildren?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
||||||
*
|
*
|
||||||
|
|
@ -19535,6 +19562,15 @@ export interface FrameLocator {
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
getByRole(role: "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|"caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|"deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|"group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|"menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|"presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|"tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem", options?: {
|
||||||
|
/**
|
||||||
|
* Option that controls whether elements referenced by `aria-owns` and `aria-controls` are included in the search. By
|
||||||
|
* default, elements referenced by `aria-owns` and `aria-controls` are not included.
|
||||||
|
*
|
||||||
|
* Learn more about [`aria-owns`](https://www.w3.org/TR/wai-aria-1.2/#aria-owns) and
|
||||||
|
* [`aria-controls`](https://www.w3.org/TR/wai-aria-1.2/#aria-controls).
|
||||||
|
*/
|
||||||
|
ariaChildren?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
139
tests/page/page-aria-children.spec.ts
Normal file
139
tests/page/page-aria-children.spec.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { test as it, expect } from './pageTest';
|
||||||
|
|
||||||
|
it('should handle aria-owns with elements outside the parent tree', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="navigation" aria-owns="menu1 menu2">
|
||||||
|
<div id="menu1" role="menu">
|
||||||
|
<div role="menuitem">Home</div>
|
||||||
|
<div role="menuitem">About</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="menu2" role="menu">
|
||||||
|
<div role="menuitem">Services</div>
|
||||||
|
<div role="menuitem">Contact</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const menuItem = page.getByRole('navigation').getByRole('menu', { ariaChildren: true }).getByRole('menuitem', { name: 'Services' });
|
||||||
|
await expect.soft(menuItem).toHaveText(`Services`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle aria-controls with elements outside the parent tree', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<form role="form" aria-controls="input1 input2">
|
||||||
|
<label for="input1">First Name</label>
|
||||||
|
<input id="input1" type="text">
|
||||||
|
</form>
|
||||||
|
<label for="input2">Last Name</label>
|
||||||
|
<input id="input2" type="text">
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.getByRole('form').getByRole('textbox', { name: 'Last Name', ariaChildren: true }).fill('John');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle aria-owns and aria-controls with elements outside the parent tree', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="main" aria-owns="section1 section2" aria-controls="footer">
|
||||||
|
<section id="section1" role="region">
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<p>Welcome to our website.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<section id="section2" role="region">
|
||||||
|
<h2>Features</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Feature 1</li>
|
||||||
|
<li>Feature 2</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer id="footer">
|
||||||
|
<p>Contact us at info@example.com</p>
|
||||||
|
</footer>
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.getByRole('main').getByRole('region', { ariaChildren: true }).getByRole('heading', { name: 'Features' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle nested roles with aria-owns', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="tree" aria-owns="node1 node2">
|
||||||
|
<div id="node1" role="treeitem">Node 1</div>
|
||||||
|
<div id="node2" role="treeitem">Node 2</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const treeItem = page.getByRole('tree').getByRole('treeitem', { name: 'Node 1' });
|
||||||
|
await expect(treeItem).toHaveText('Node 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle aria-controls with nested elements', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="tablist" aria-controls="panel1 panel2">
|
||||||
|
<div role="tab" id="tab1">Tab 1</div>
|
||||||
|
<div role="tab" id="tab2">Tab 2</div>
|
||||||
|
</div>
|
||||||
|
<div id="panel1" role="tabpanel">Panel 1 Content</div>
|
||||||
|
<div id="panel2" role="tabpanel">Panel 2 Content</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const tabPanel = page.getByRole('tablist').getByRole('tabpanel', { ariaChildren: true }).getByText('Panel 1 Content');
|
||||||
|
await expect(tabPanel).toHaveText('Panel 1 Content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle accordion with aria-controls', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="region">
|
||||||
|
<button aria-controls="section1">Section 1</button>
|
||||||
|
<button aria-controls="section2">Section 2</button>
|
||||||
|
</div>
|
||||||
|
<div id="section1" role="region">Section 1 Content</div>
|
||||||
|
<div id="section2" role="region">Section 2 Content</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const section = page.getByRole('region').getByRole('button', { name: 'Section 1' }).getByRole('region', { ariaChildren: true });
|
||||||
|
await expect(section).toHaveText('Section 1 Content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle aria-owns with mixed roles', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="grid" aria-owns="row1 row2">
|
||||||
|
<div id="row1" role="row">
|
||||||
|
<div role="gridcell">Cell 1</div>
|
||||||
|
</div>
|
||||||
|
<div id="row2" role="row">
|
||||||
|
<div role="gridcell">Cell 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const gridCell = page.getByRole('grid').getByRole('gridcell', { name: 'Cell 1' });
|
||||||
|
await expect(gridCell).toHaveText('Cell 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should handle aria-owns with role changes', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div role="tablist" aria-owns="tab1 tab2">
|
||||||
|
<div id="tab1" role="tab">Tab 1</div>
|
||||||
|
<div id="tab2" role="tab">Tab 2</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const tab = page.getByRole('tablist').getByRole('tab', { name: 'Tab 1' });
|
||||||
|
await expect(tab).toHaveText('Tab 1');
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue