From 4a275b8ecae906f1859fa0fe0d77fdab6d8bfd4e Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 22 Apr 2024 12:33:30 -0700 Subject: [PATCH] feat: expect(locator).toHaveAccessibleDescription (#30463) References #18332. --- docs/src/api/class-locatorassertions.md | 69 +++++++++++++++++++ .../src/server/injected/injectedScript.ts | 2 + packages/playwright/src/matchers/expect.ts | 2 + packages/playwright/src/matchers/matchers.ts | 12 ++++ packages/playwright/types/test.d.ts | 27 ++++++++ tests/page/expect-misc.spec.ts | 12 ++++ 6 files changed, 124 insertions(+) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 4124594c5a..72bad1940a 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -224,6 +224,25 @@ Whether to use `element.innerText` instead of `element.textContent` when retriev * since: v1.18 +## async method: LocatorAssertions.NotToHaveAccessibleDescription +* since: v1.44 +* langs: python + +The opposite of [`method: LocatorAssertions.toHaveAccessibleDescription`]. + +### param: LocatorAssertions.NotToHaveAccessibleDescription.name +* since: v1.44 +- `name` <[string]|[RegExp]> + +Expected accessible name. + +### option: LocatorAssertions.NotToHaveAccessibleDescription.ignoreCase = %%-assertions-ignore-case-%% +* since: v1.44 + +### option: LocatorAssertions.NotToHaveAccessibleDescription.timeout = %%-csharp-java-python-assertions-timeout-%% +* since: v1.44 + + ## async method: LocatorAssertions.NotToHaveAccessibleName * since: v1.44 * langs: python @@ -1116,6 +1135,56 @@ Whether to use `element.innerText` instead of `element.textContent` when retriev * since: v1.18 +## async method: LocatorAssertions.toHaveAccessibleDescription +* since: v1.44 +* langs: + - alias-java: hasAccessibleDescription + +Ensures the [Locator] points to an element with a given [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + +**Usage** + +```js +const locator = page.getByTestId('save-button'); +await expect(locator).toHaveAccessibleDescription('Save results to disk'); +``` + +```java +Locator locator = page.getByTestId("save-button"); +assertThat(locator).hasAccessibleDescription("Save results to disk"); +``` + +```python async +locator = page.get_by_test_id("save-button") +await expect(locator).to_have_accessible_description("Save results to disk") +``` + +```python sync +locator = page.get_by_test_id("save-button") +expect(locator).to_have_accessible_description("Save results to disk") +``` + +```csharp +var locator = Page.GetByTestId("save-button"); +await Expect(locator).toHaveAccessibleDescriptionAsync("Save results to disk"); +``` + +### param: LocatorAssertions.toHaveAccessibleDescription.description +* since: v1.44 +- `description` <[string]|[RegExp]> + +Expected accessible description. + +### option: LocatorAssertions.toHaveAccessibleDescription.timeout = %%-js-assertions-timeout-%% +* since: v1.44 + +### option: LocatorAssertions.toHaveAccessibleDescription.timeout = %%-csharp-java-python-assertions-timeout-%% +* since: v1.44 + +### option: LocatorAssertions.toHaveAccessibleDescription.ignoreCase = %%-assertions-ignore-case-%% +* since: v1.44 + + ## async method: LocatorAssertions.toHaveAccessibleName * since: v1.44 * langs: diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index aca1865df5..6f829a7924 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -1225,6 +1225,8 @@ export class InjectedScript { received = options.useInnerText ? (element as HTMLElement).innerText : elementText(new Map(), element).full; } else if (expression === 'to.have.accessible.name') { received = getElementAccessibleName(element, false /* includeHidden */); + } else if (expression === 'to.have.accessible.description') { + received = getElementAccessibleDescription(element, false /* includeHidden */); } else if (expression === 'to.have.title') { received = this.document.title; } else if (expression === 'to.have.url') { diff --git a/packages/playwright/src/matchers/expect.ts b/packages/playwright/src/matchers/expect.ts index 9145086b26..e1ce5470f0 100644 --- a/packages/playwright/src/matchers/expect.ts +++ b/packages/playwright/src/matchers/expect.ts @@ -32,6 +32,7 @@ import { toBeOK, toBeVisible, toContainText, + toHaveAccessibleDescription, toHaveAccessibleName, toHaveAttribute, toHaveClass, @@ -186,6 +187,7 @@ const customAsyncMatchers = { toBeOK, toBeVisible, toContainText, + toHaveAccessibleDescription, toHaveAccessibleName, toHaveAttribute, toHaveClass, diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index d6590ce11d..5145674673 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -174,6 +174,18 @@ export function toContainText( } } +export function toHaveAccessibleDescription( + this: ExpectMatcherContext, + locator: LocatorEx, + expected: string | RegExp, + options?: { timeout?: number, ignoreCase?: boolean }, +) { + return toMatchText.call(this, 'toHaveAccessibleDescription', locator, 'Locator', async (isNot, timeout) => { + const expectedText = toExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); + return await locator._expect('to.have.accessible.description', { expectedText, isNot, timeout }); + }, expected, options); +} + export function toHaveAccessibleName( this: ExpectMatcherContext, locator: LocatorEx, diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index cfdccad022..df4783a75d 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6882,6 +6882,33 @@ interface LocatorAssertions { useInnerText?: boolean; }): Promise; + /** + * Ensures the {@link Locator} points to an element with a given + * [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + * + * **Usage** + * + * ```js + * const locator = page.getByTestId('save-button'); + * await expect(locator).toHaveAccessibleDescription('Save results to disk'); + * ``` + * + * @param description Expected accessible description. + * @param options + */ + toHaveAccessibleDescription(description: string|RegExp, options?: { + /** + * Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular + * expression flag if specified. + */ + ignoreCase?: boolean; + + /** + * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. + */ + timeout?: number; + }): Promise; + /** * Ensures the {@link Locator} points to an element with a given * [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index 8f5bf81201..66e09a51df 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -431,3 +431,15 @@ test('toHaveAccessibleName', async ({ page }) => { await expect(page.locator('div')).not.toHaveAccessibleName(/hello/); await expect(page.locator('div')).toHaveAccessibleName(/hello/, { ignoreCase: true }); }); + +test('toHaveAccessibleDescription', async ({ page }) => { + await page.setContent(` +
+ `); + await expect(page.locator('div')).toHaveAccessibleDescription('Hello'); + await expect(page.locator('div')).not.toHaveAccessibleDescription('hello'); + await expect(page.locator('div')).toHaveAccessibleDescription('hello', { ignoreCase: true }); + await expect(page.locator('div')).toHaveAccessibleDescription(/ell\w/); + await expect(page.locator('div')).not.toHaveAccessibleDescription(/hello/); + await expect(page.locator('div')).toHaveAccessibleDescription(/hello/, { ignoreCase: true }); +});