Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aria): extend toHaveAccessibleName() to accept an array of expected accessible names #33277

Merged
merged 8 commits into from
Nov 18, 2024
2 changes: 1 addition & 1 deletion docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,7 @@ await Expect(locator).toHaveAccessibleNameAsync("Save to disk");

### param: LocatorAssertions.toHaveAccessibleName.name
* since: v1.44
- `name` <[string]|[RegExp]>
- `name` <[string]|[RegExp]|[Array]<[string]>|[Array]<[RegExp]>>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be [Array]<[string]|[RegExp]>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the type format


Expected accessible name.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,8 @@ export class InjectedScript {
received = elements.map(e => options.useInnerText ? (e as HTMLElement).innerText : elementText(new Map(), e).full);
else if (expression === 'to.have.class.array')
received = elements.map(e => e.classList.toString());
else if (expression === 'to.have.accessible.name.array')
received = elements.map(e => getElementAccessibleName(e, false));

if (received && options.expectedText) {
// "To match an array" is "to contain an array" + "equal length"
Expand Down
19 changes: 13 additions & 6 deletions packages/playwright/src/matchers/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,20 @@ export function toHaveAccessibleDescription(
export function toHaveAccessibleName(
this: ExpectMatcherState,
locator: LocatorEx,
expected: string | RegExp,
options?: { timeout?: number, ignoreCase?: boolean },
expected: string | RegExp | (string | RegExp)[],
options: { timeout?: number, ignoreCase?: boolean, normalizeWhiteSpace?: boolean } = {}
) {
return toMatchText.call(this, 'toHaveAccessibleName', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
return await locator._expect('to.have.accessible.name', { expectedText, isNot, timeout });
}, expected, options);
if (Array.isArray(expected)) {
return toEqual.call(this, 'toHaveAccessibleName', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues(expected, { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the changes to normalizeWhiteSpace and matchSubstring to be in a separate PR, and discuss them in an issue first - especially the matchSubstring one sounds breaking to me.

Let's keep this PR strictly about accepting an array.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, let's only land the array aspect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I excluded the matchSubstring option from the string serialization

return await locator._expect('to.have.accessible.name.array', { expectedText, isNot, timeout });
}, expected, options);
} else {
return toMatchText.call(this, 'toHaveAccessibleName', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
return await locator._expect('to.have.accessible.name', { expectedText, isNot, timeout });
}, expected, options);
}
}

export function toHaveAttribute(
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7899,7 +7899,7 @@ interface LocatorAssertions {
* @param name Expected accessible name.
* @param options
*/
toHaveAccessibleName(name: string|RegExp, options?: {
toHaveAccessibleName(name: string|RegExp|ReadonlyArray<string>|ReadonlyArray<RegExp>, options?: {
/**
* Whether to perform case-insensitive match.
* [`ignoreCase`](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-accessible-name-option-ignore-case)
Expand Down
33 changes: 33 additions & 0 deletions tests/page/expect-misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,39 @@ test('toHaveAccessibleName', async ({ page }) => {
await expect(page.locator('div')).toHaveAccessibleName(/hello/, { ignoreCase: true });
});

test('toHaveAccessibleName should accept array of names for multiple elements', async ({ page }) => {
await page.setContent(`
<table>
<tr role="row">
<td role="cell">Cell A1</td>
<td role="cell">Cell B1</td>
<td role="cell">Cell C1</td>
</tr>
<tr role="row">
<td role="cell">Cell A2</td>
<td role="cell">Cell B2</td>
<td role="cell">Cell C2</td>
</tr>
<tr role="row">
<td role="cell">Cell A3</td>
<td role="cell">Cell B3</td>
<td role="cell">Cell C3</td>
</tr>
</table>
`);
await expect(page.getByRole('row')).toHaveAccessibleName([
'Cell A1 Cell B1 Cell C1',
'Cell A2 Cell B2 Cell C2',
'Cell A3 Cell B3 Cell C3',
]);
await expect(page.getByRole('row')).toHaveAccessibleName(['Cell A1 Cell B1 Cell C1',
'Cell A2 Cell B2 Cell C2',
'Cell A3 Cell B3',]);
await expect(page.getByRole('row')).toHaveAccessibleName(['cell a1 cell b1 cell C1',
'cell A2 Cell b2 Cell c2',
'Cell a3 Cell b3 cell C3',], { ignoreCase: true });
});

test('toHaveAccessibleDescription', async ({ page }) => {
await page.setContent(`
<div role="button" aria-description="Hello"></div>
Expand Down
Loading