feat: add toHaveAccessibleErrorMessage
This commit is contained in:
parent
37e155a8a1
commit
b3e3545383
|
|
@ -29,7 +29,7 @@ import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser';
|
||||||
import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator';
|
import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import { Highlight } from './highlight';
|
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 { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
|
||||||
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
||||||
import type { Language } from '../../utils/isomorphic/locatorGenerators';
|
import type { Language } from '../../utils/isomorphic/locatorGenerators';
|
||||||
|
|
@ -1321,6 +1321,8 @@ export class InjectedScript {
|
||||||
received = getElementAccessibleName(element, false /* includeHidden */);
|
received = getElementAccessibleName(element, false /* includeHidden */);
|
||||||
} else if (expression === 'to.have.accessible.description') {
|
} else if (expression === 'to.have.accessible.description') {
|
||||||
received = getElementAccessibleDescription(element, false /* includeHidden */);
|
received = getElementAccessibleDescription(element, false /* includeHidden */);
|
||||||
|
} else if (expression === 'to.have.accessible.error.message') {
|
||||||
|
received = getElementAccessibleErrorMessage(element, false /* includeHidden */);
|
||||||
} else if (expression === 'to.have.role') {
|
} else if (expression === 'to.have.role') {
|
||||||
received = getAriaRole(element) || '';
|
received = getAriaRole(element) || '';
|
||||||
} else if (expression === 'to.have.title') {
|
} else if (expression === 'to.have.title') {
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,38 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
|
||||||
return accessibleDescription;
|
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 = {
|
type AccessibleNameOptions = {
|
||||||
visitedElements: Set<Element>,
|
visitedElements: Set<Element>,
|
||||||
includeHidden?: boolean,
|
includeHidden?: boolean,
|
||||||
|
|
@ -972,6 +1004,8 @@ let cacheAccessibleName: Map<Element, string> | undefined;
|
||||||
let cacheAccessibleNameHidden: Map<Element, string> | undefined;
|
let cacheAccessibleNameHidden: Map<Element, string> | undefined;
|
||||||
let cacheAccessibleDescription: Map<Element, string> | undefined;
|
let cacheAccessibleDescription: Map<Element, string> | undefined;
|
||||||
let cacheAccessibleDescriptionHidden: 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 cacheIsHidden: Map<Element, boolean> | undefined;
|
||||||
let cachePseudoContentBefore: Map<Element, string> | undefined;
|
let cachePseudoContentBefore: Map<Element, string> | undefined;
|
||||||
let cachePseudoContentAfter: Map<Element, string> | undefined;
|
let cachePseudoContentAfter: Map<Element, string> | undefined;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import {
|
||||||
toContainText,
|
toContainText,
|
||||||
toHaveAccessibleDescription,
|
toHaveAccessibleDescription,
|
||||||
toHaveAccessibleName,
|
toHaveAccessibleName,
|
||||||
|
toHaveAccessibleErrorMessage,
|
||||||
toHaveAttribute,
|
toHaveAttribute,
|
||||||
toHaveClass,
|
toHaveClass,
|
||||||
toHaveCount,
|
toHaveCount,
|
||||||
|
|
@ -224,6 +225,7 @@ const customAsyncMatchers = {
|
||||||
toContainText,
|
toContainText,
|
||||||
toHaveAccessibleDescription,
|
toHaveAccessibleDescription,
|
||||||
toHaveAccessibleName,
|
toHaveAccessibleName,
|
||||||
|
toHaveAccessibleErrorMessage,
|
||||||
toHaveAttribute,
|
toHaveAttribute,
|
||||||
toHaveClass,
|
toHaveClass,
|
||||||
toHaveCount,
|
toHaveCount,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
export function toHaveAttribute(
|
||||||
this: ExpectMatcherState,
|
this: ExpectMatcherState,
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue