mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
feat: expect(locator).toHaveRole(role) (#30555)
References #13517. Fixes #18332.
This commit is contained in:
parent
9a1b34a4b0
commit
6d20da568e
@ -1030,7 +1030,8 @@ Attribute name to get the value for.
|
||||
|
||||
%%-template-locator-get-by-role-%%
|
||||
|
||||
### param: Frame.getByRole.role = %%-locator-get-by-role-role-%%
|
||||
### param: Frame.getByRole.role = %%-get-by-role-to-have-role-role-%%
|
||||
* since: v1.27
|
||||
|
||||
### option: Frame.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
|
||||
* since: v1.27
|
||||
|
@ -133,7 +133,8 @@ in that iframe.
|
||||
|
||||
%%-template-locator-get-by-role-%%
|
||||
|
||||
### param: FrameLocator.getByRole.role = %%-locator-get-by-role-role-%%
|
||||
### param: FrameLocator.getByRole.role = %%-get-by-role-to-have-role-role-%%
|
||||
* since: v1.27
|
||||
|
||||
### option: FrameLocator.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
|
||||
* since: v1.27
|
||||
|
@ -1173,7 +1173,8 @@ Attribute name to get the value for.
|
||||
|
||||
%%-template-locator-get-by-role-%%
|
||||
|
||||
### param: Locator.getByRole.role = %%-locator-get-by-role-role-%%
|
||||
### param: Locator.getByRole.role = %%-get-by-role-to-have-role-role-%%
|
||||
* since: v1.27
|
||||
|
||||
### option: Locator.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
|
||||
* since: v1.27
|
||||
|
@ -373,6 +373,23 @@ Property value.
|
||||
### option: LocatorAssertions.NotToHaveJSProperty.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.18
|
||||
|
||||
|
||||
## async method: LocatorAssertions.NotToHaveRole
|
||||
* since: v1.44
|
||||
* langs: python
|
||||
|
||||
The opposite of [`method: LocatorAssertions.toHaveRole`].
|
||||
|
||||
### param: LocatorAssertions.NotToHaveRole.name
|
||||
* since: v1.44
|
||||
- `name` <[string]|[RegExp]>
|
||||
|
||||
Expected accessible name.
|
||||
|
||||
### option: LocatorAssertions.NotToHaveRole.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.44
|
||||
|
||||
|
||||
## async method: LocatorAssertions.NotToHaveText
|
||||
* since: v1.20
|
||||
* langs: python
|
||||
@ -1629,6 +1646,53 @@ Property value.
|
||||
### option: LocatorAssertions.toHaveJSProperty.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.18
|
||||
|
||||
|
||||
## async method: LocatorAssertions.toHaveRole
|
||||
* since: v1.44
|
||||
* langs:
|
||||
- alias-java: hasRole
|
||||
|
||||
Ensures the [Locator] points to an element with a given [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles).
|
||||
|
||||
Note that role is matched as a string, disregarding the ARIA role hierarchy. For example, asserting a superclass role `"checkbox"` on an element with a subclass role `"switch"` will fail.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
const locator = page.getByTestId('save-button');
|
||||
await expect(locator).toHaveRole('button');
|
||||
```
|
||||
|
||||
```java
|
||||
Locator locator = page.getByTestId("save-button");
|
||||
assertThat(locator).hasRole(AriaRole.BUTTON);
|
||||
```
|
||||
|
||||
```python async
|
||||
locator = page.get_by_test_id("save-button")
|
||||
await expect(locator).to_have_role("button")
|
||||
```
|
||||
|
||||
```python sync
|
||||
locator = page.get_by_test_id("save-button")
|
||||
expect(locator).to_have_role("button")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var locator = Page.GetByTestId("save-button");
|
||||
await Expect(locator).ToHaveRoleAsync(AriaRole.Button);
|
||||
```
|
||||
|
||||
### param: LocatorAssertions.toHaveRole.role = %%-get-by-role-to-have-role-role-%%
|
||||
* since: v1.44
|
||||
|
||||
### option: LocatorAssertions.toHaveRole.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.44
|
||||
|
||||
### option: LocatorAssertions.toHaveRole.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.44
|
||||
|
||||
|
||||
## async method: LocatorAssertions.toHaveScreenshot#1
|
||||
* since: v1.23
|
||||
* langs: js
|
||||
|
@ -2340,7 +2340,8 @@ Attribute name to get the value for.
|
||||
|
||||
%%-template-locator-get-by-role-%%
|
||||
|
||||
### param: Page.getByRole.role = %%-locator-get-by-role-role-%%
|
||||
### param: Page.getByRole.role = %%-get-by-role-to-have-role-role-%%
|
||||
* since: v1.27
|
||||
|
||||
### option: Page.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
|
||||
* since: v1.27
|
||||
|
@ -1203,8 +1203,7 @@ Text to locate the element for.
|
||||
|
||||
Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular expression. Note that exact match still trims whitespace.
|
||||
|
||||
## locator-get-by-role-role
|
||||
* since: v1.27
|
||||
## get-by-role-to-have-role-role
|
||||
- `role` <[AriaRole]<"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">>
|
||||
|
||||
Required aria role.
|
||||
|
@ -18,12 +18,15 @@ title: "Assertions"
|
||||
| [`method: LocatorAssertions.toBeInViewport`] | Element intersects viewport |
|
||||
| [`method: LocatorAssertions.toBeVisible`] | Element is visible |
|
||||
| [`method: LocatorAssertions.toContainText`] | Element contains text |
|
||||
| [`method: LocatorAssertions.toHaveAccessibleDescription`] | Element has a matching [accessible description](https://w3c.github.io/accname/#dfn-accessible-description) |
|
||||
| [`method: LocatorAssertions.toHaveAccessibleName`] | Element has a matching [accessible name](https://w3c.github.io/accname/#dfn-accessible-name) |
|
||||
| [`method: LocatorAssertions.toHaveAttribute`] | Element has a DOM attribute |
|
||||
| [`method: LocatorAssertions.toHaveClass`] | Element has a class property |
|
||||
| [`method: LocatorAssertions.toHaveCount`] | List has exact number of children |
|
||||
| [`method: LocatorAssertions.toHaveCSS`] | Element has CSS property |
|
||||
| [`method: LocatorAssertions.toHaveId`] | Element has an ID |
|
||||
| [`method: LocatorAssertions.toHaveJSProperty`] | Element has a JavaScript property |
|
||||
| [`method: LocatorAssertions.toHaveRole`] | Element has a specific [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles) |
|
||||
| [`method: LocatorAssertions.toHaveText`] | Element matches text |
|
||||
| [`method: LocatorAssertions.toHaveValue`] | Input has a value |
|
||||
| [`method: LocatorAssertions.toHaveValues`] | Select has options selected |
|
||||
|
@ -40,12 +40,15 @@ Note that retrying assertions are async, so you must `await` them.
|
||||
| [await expect(locator).toBeInViewport()](./api/class-locatorassertions.md#locator-assertions-to-be-in-viewport) | Element intersects viewport |
|
||||
| [await expect(locator).toBeVisible()](./api/class-locatorassertions.md#locator-assertions-to-be-visible) | Element is visible |
|
||||
| [await expect(locator).toContainText()](./api/class-locatorassertions.md#locator-assertions-to-contain-text) | Element contains text |
|
||||
| [await expect(locator).toHaveAccessibleDescription()](./api/class-locatorassertions.md#locator-assertions-to-have-accessible-description) | Element has a matching [accessible description](https://w3c.github.io/accname/#dfn-accessible-description) |
|
||||
| [await expect(locator).toHaveAccessibleName()](./api/class-locatorassertions.md#locator-assertions-to-have-accessible-name) | Element has a matching [accessible name](https://w3c.github.io/accname/#dfn-accessible-name) |
|
||||
| [await expect(locator).toHaveAttribute()](./api/class-locatorassertions.md#locator-assertions-to-have-attribute) | Element has a DOM attribute |
|
||||
| [await expect(locator).toHaveClass()](./api/class-locatorassertions.md#locator-assertions-to-have-class) | Element has a class property |
|
||||
| [await expect(locator).toHaveCount()](./api/class-locatorassertions.md#locator-assertions-to-have-count) | List has exact number of children |
|
||||
| [await expect(locator).toHaveCSS()](./api/class-locatorassertions.md#locator-assertions-to-have-css) | Element has CSS property |
|
||||
| [await expect(locator).toHaveId()](./api/class-locatorassertions.md#locator-assertions-to-have-id) | Element has an ID |
|
||||
| [await expect(locator).toHaveJSProperty()](./api/class-locatorassertions.md#locator-assertions-to-have-js-property) | Element has a JavaScript property |
|
||||
| [await expect(locator).toHaveRole()](./api/class-locatorassertions.md#locator-assertions-to-have-role) | Element has a specific [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles) |
|
||||
| [await expect(locator).toHaveScreenshot()](./api/class-locatorassertions.md#locator-assertions-to-have-screenshot-1) | Element has a screenshot |
|
||||
| [await expect(locator).toHaveText()](./api/class-locatorassertions.md#locator-assertions-to-have-text) | Element matches text |
|
||||
| [await expect(locator).toHaveValue()](./api/class-locatorassertions.md#locator-assertions-to-have-value) | Input has a value |
|
||||
|
@ -1227,6 +1227,8 @@ export class InjectedScript {
|
||||
received = getElementAccessibleName(element, false /* includeHidden */);
|
||||
} else if (expression === 'to.have.accessible.description') {
|
||||
received = getElementAccessibleDescription(element, false /* includeHidden */);
|
||||
} else if (expression === 'to.have.role') {
|
||||
received = getAriaRole(element) || '';
|
||||
} else if (expression === 'to.have.title') {
|
||||
received = this.document.title;
|
||||
} else if (expression === 'to.have.url') {
|
||||
|
@ -40,6 +40,7 @@ import {
|
||||
toHaveCSS,
|
||||
toHaveId,
|
||||
toHaveJSProperty,
|
||||
toHaveRole,
|
||||
toHaveText,
|
||||
toHaveTitle,
|
||||
toHaveURL,
|
||||
@ -195,6 +196,7 @@ const customAsyncMatchers = {
|
||||
toHaveCSS,
|
||||
toHaveId,
|
||||
toHaveJSProperty,
|
||||
toHaveRole,
|
||||
toHaveText,
|
||||
toHaveTitle,
|
||||
toHaveURL,
|
||||
|
@ -21,7 +21,7 @@ import { expectTypes, callLogText } from '../util';
|
||||
import { toBeTruthy } from './toBeTruthy';
|
||||
import { toEqual } from './toEqual';
|
||||
import { toExpectedTextValues, toMatchText } from './toMatchText';
|
||||
import { constructURLBasedOnBaseURL, isRegExp, isTextualMimeType, pollAgainstDeadline } from 'playwright-core/lib/utils';
|
||||
import { constructURLBasedOnBaseURL, isRegExp, isString, isTextualMimeType, pollAgainstDeadline } from 'playwright-core/lib/utils';
|
||||
import { currentTestInfo } from '../common/globals';
|
||||
import { TestInfoImpl } from '../worker/testInfo';
|
||||
import type { ExpectMatcherContext } from './expect';
|
||||
@ -290,6 +290,20 @@ export function toHaveJSProperty(
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export function toHaveRole(
|
||||
this: ExpectMatcherContext,
|
||||
locator: LocatorEx,
|
||||
expected: string,
|
||||
options?: { timeout?: number, ignoreCase?: boolean },
|
||||
) {
|
||||
if (!isString(expected))
|
||||
throw new Error(`"role" argument in toHaveRole must be a string`);
|
||||
return toMatchText.call(this, 'toHaveRole', locator, 'Locator', async (isNot, timeout) => {
|
||||
const expectedText = toExpectedTextValues([expected]);
|
||||
return await locator._expect('to.have.role', { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
|
||||
export function toHaveText(
|
||||
this: ExpectMatcherContext,
|
||||
locator: LocatorEx,
|
||||
|
24
packages/playwright/types/test.d.ts
vendored
24
packages/playwright/types/test.d.ts
vendored
@ -7141,6 +7141,30 @@ interface LocatorAssertions {
|
||||
timeout?: number;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Ensures the {@link Locator} points to an element with a given
|
||||
* [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles).
|
||||
*
|
||||
* Note that role is matched as a string, disregarding the ARIA role hierarchy. For example, asserting a superclass
|
||||
* role `"checkbox"` on an element with a subclass role `"switch"` will fail.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
* ```js
|
||||
* const locator = page.getByTestId('save-button');
|
||||
* await expect(locator).toHaveRole('button');
|
||||
* ```
|
||||
*
|
||||
* @param role Required aria role.
|
||||
* @param options
|
||||
*/
|
||||
toHaveRole(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?: {
|
||||
/**
|
||||
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
|
||||
*/
|
||||
timeout?: number;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* This function will wait until two consecutive locator screenshots yield the same result, and then compare the last
|
||||
* screenshot with the expectation.
|
||||
|
@ -443,3 +443,16 @@ test('toHaveAccessibleDescription', async ({ page }) => {
|
||||
await expect(page.locator('div')).not.toHaveAccessibleDescription(/hello/);
|
||||
await expect(page.locator('div')).toHaveAccessibleDescription(/hello/, { ignoreCase: true });
|
||||
});
|
||||
|
||||
test('toHaveRole', async ({ page }) => {
|
||||
await page.setContent(`<div role="button">Button!</div>`);
|
||||
await expect(page.locator('div')).toHaveRole('button');
|
||||
await expect(page.locator('div')).not.toHaveRole('checkbox');
|
||||
try {
|
||||
// @ts-expect-error
|
||||
await expect(page.locator('div')).toHaveRole(/button|checkbox/);
|
||||
expect(1, 'Must throw when given a regular expression').toBe(2);
|
||||
} catch (error) {
|
||||
expect(error.message).toBe(`"role" argument in toHaveRole must be a string`);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user