chore: make role name case-insensitive (#17888)

This commit is contained in:
Pavel Feldman 2022-10-06 13:35:10 -08:00 committed by GitHub
parent 43208da3f8
commit 8b018f6b41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 12 additions and 8 deletions

View File

@ -396,7 +396,7 @@ export function setTestIdAttribute(attributeName: string) {
function getByAttributeTextSelector(attrName: string, text: string | RegExp, options?: { exact?: boolean }): string {
if (!isString(text))
return `internal:attr=[${attrName}=${text}]`;
return `internal:attr=[${attrName}=${escapeForAttributeSelector(text)}${options?.exact ? 's' : 'i'}]`;
return `internal:attr=[${attrName}=${escapeForAttributeSelector(text, options?.exact || false)}]`;
}
export function getByTestIdSelector(testId: string): string {
@ -439,7 +439,7 @@ export function getByRoleSelector(role: string, options: ByRoleOptions = {}): st
if (options.level !== undefined)
props.push(['level', String(options.level)]);
if (options.name !== undefined)
props.push(['name', isString(options.name) ? escapeForAttributeSelector(options.name) : String(options.name)]);
props.push(['name', isString(options.name) ? escapeForAttributeSelector(options.name, false) : String(options.name)]);
if (options.pressed !== undefined)
props.push(['pressed', String(options.pressed)]);
return `role=${role}${props.map(([n, v]) => `[${n}=${v}]`).join('')}`;

View File

@ -148,7 +148,7 @@ function buildCandidates(injectedScript: InjectedScript, element: Element, acces
const candidates: SelectorToken[] = [];
if (element.getAttribute('data-testid'))
candidates.push({ engine: 'internal:attr', selector: `[data-testid=${escapeForAttributeSelector(element.getAttribute('data-testid')!)}]`, score: 1 });
candidates.push({ engine: 'internal:attr', selector: `[data-testid=${escapeForAttributeSelector(element.getAttribute('data-testid')!, true)}]`, score: 1 });
for (const attr of ['data-test-id', 'data-test']) {
if (element.getAttribute(attr))
@ -158,7 +158,7 @@ function buildCandidates(injectedScript: InjectedScript, element: Element, acces
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
const input = element as HTMLInputElement | HTMLTextAreaElement;
if (input.placeholder)
candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder)}]`, score: 3 });
candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder, true)}]`, score: 3 });
const label = input.labels?.[0];
if (label) {
const labelText = elementText(injectedScript._evaluator._cacheText, label).full.trim();
@ -170,13 +170,13 @@ function buildCandidates(injectedScript: InjectedScript, element: Element, acces
if (ariaRole) {
const ariaName = getElementAccessibleName(element, false, accessibleNameCache);
if (ariaName)
candidates.push({ engine: 'role', selector: `${ariaRole}[name=${escapeForAttributeSelector(ariaName)}]`, score: 3 });
candidates.push({ engine: 'role', selector: `${ariaRole}[name=${escapeForAttributeSelector(ariaName, true)}]`, score: 3 });
else
candidates.push({ engine: 'role', selector: ariaRole, score: 150 });
}
if (element.getAttribute('alt') && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName))
candidates.push({ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(element.getAttribute('alt')!)}]`, score: 10 });
candidates.push({ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(element.getAttribute('alt')!, true)}]`, score: 10 });
if (element.getAttribute('name') && ['BUTTON', 'FORM', 'FIELDSET', 'FRAME', 'IFRAME', 'INPUT', 'KEYGEN', 'OBJECT', 'OUTPUT', 'SELECT', 'TEXTAREA', 'MAP', 'META', 'PARAM'].includes(element.nodeName))
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[name=${quoteAttributeValue(element.getAttribute('name')!)}]`, score: 50 });

View File

@ -72,10 +72,10 @@ export function escapeForTextSelector(text: string | RegExp, exact: boolean, cas
return text;
}
export function escapeForAttributeSelector(value: string): string {
export function escapeForAttributeSelector(value: string, exact: boolean): string {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return `"${value.replace(/["]/g, '\\"')}"`;
return `"${value.replace(/["]/g, '\\"')}"${exact ? '' : 'i'}`;
}

View File

@ -360,6 +360,10 @@ test('should support name', async ({ page }) => {
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hello" aria-hidden="true"></div>`,
]);
expect(await page.getByRole('button', { name: 'hello', includeHidden: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hello" aria-hidden="true"></div>`,
]);
expect(await page.locator(`role=button[name=Hello]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,