Skip to content

Commit

Permalink
feat: add toHaveAccessibleErrorMessage
Browse files Browse the repository at this point in the history
  • Loading branch information
pengooseDev committed Dec 7, 2024
1 parent 7e310f7 commit df041e4
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser';
import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator';
import type * as channels from '@protocol/channels';
import { Highlight } from './highlight';
import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription, getReadonly } from './roleUtils';
import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription, getReadonly, getElementAccessibleErrorMessage } from './roleUtils';
import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
import type { Language } from '../../utils/isomorphic/locatorGenerators';
Expand Down Expand Up @@ -1320,6 +1320,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.accessible.error.message') {
received = getElementAccessibleErrorMessage(element, false /* includeHidden */);
} else if (expression === 'to.have.role') {
received = getAriaRole(element) || '';
} else if (expression === 'to.have.title') {
Expand Down
34 changes: 34 additions & 0 deletions packages/playwright-core/src/server/injected/roleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,38 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
return accessibleDescription;
}

export function getElementAccessibleErrorMessage(element: Element, includeHidden: boolean): string {
const cache = includeHidden ? cacheAccessibleErrorMessageHidden : cacheAccessibleErrorMessage;
let accessibleErrorMessage = cache?.get(element);

if (accessibleErrorMessage === undefined) {
accessibleErrorMessage = '';

const ariaInvalid = element.getAttribute('aria-invalid');
if (ariaInvalid === 'true') {
const errorMessageId = element.getAttribute('aria-errormessage');
if (errorMessageId) {
// Ensure the ID is valid (no whitespace)
if (!/\s+/.test(errorMessageId)) {
// Retrieve the element referenced by aria-errormessage.
const errorElement = element.ownerDocument.getElementById(errorMessageId);
if (errorElement) {
accessibleErrorMessage = asFlatString(
getTextAlternativeInternal(errorElement, {
includeHidden,
visitedElements: new Set(),
embeddedInDescribedBy: { element: errorElement, hidden: isElementHiddenForAria(errorElement) },
})
);
}
}
}
}
cache?.set(element, accessibleErrorMessage);
}
return accessibleErrorMessage;
}

type AccessibleNameOptions = {
visitedElements: Set<Element>,
includeHidden?: boolean,
Expand Down Expand Up @@ -972,6 +1004,8 @@ let cacheAccessibleName: Map<Element, string> | undefined;
let cacheAccessibleNameHidden: Map<Element, string> | undefined;
let cacheAccessibleDescription: Map<Element, string> | undefined;
let cacheAccessibleDescriptionHidden: Map<Element, string> | undefined;
let cacheAccessibleErrorMessage: Map<Element, string> | undefined;
let cacheAccessibleErrorMessageHidden: Map<Element, string> | undefined;
let cacheIsHidden: Map<Element, boolean> | undefined;
let cachePseudoContentBefore: Map<Element, string> | undefined;
let cachePseudoContentAfter: Map<Element, string> | undefined;
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright/src/matchers/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
toContainText,
toHaveAccessibleDescription,
toHaveAccessibleName,
toHaveAccessibleErrorMessage,
toHaveAttribute,
toHaveClass,
toHaveCount,
Expand Down Expand Up @@ -224,6 +225,7 @@ const customAsyncMatchers = {
toContainText,
toHaveAccessibleDescription,
toHaveAccessibleName,
toHaveAccessibleErrorMessage,
toHaveAttribute,
toHaveClass,
toHaveCount,
Expand Down
12 changes: 12 additions & 0 deletions packages/playwright/src/matchers/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ export function toHaveAccessibleName(
}
}

export function toHaveAccessibleErrorMessage(
this: ExpectMatcherState,
locator: LocatorEx,
expected: string | RegExp,
options?: { timeout?: number; ignoreCase?: boolean },
) {
return toMatchText.call(this, 'toHaveAccessibleErrorMessage', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
return await locator._expect('to.have.accessible.error.message', { expectedText: expectedText, isNot, timeout });
}, expected, options);
}

export function toHaveAttribute(
this: ExpectMatcherState,
locator: LocatorEx,
Expand Down

0 comments on commit df041e4

Please sign in to comment.