chore: generate getByLabel for inputs (#17845)

This commit is contained in:
Pavel Feldman 2022-10-05 11:02:15 -08:00 committed by GitHub
parent e8413264fa
commit c168f5494f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 4 deletions

View File

@ -81,7 +81,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
// Do not use regex for parent elements (for performance).
textCandidates = filterRegexTokens(textCandidates);
}
const noTextCandidates = buildCandidates(element, accessibleNameCache).map(token => [token]);
const noTextCandidates = buildCandidates(injectedScript, element, accessibleNameCache).map(token => [token]);
// First check all text and non-text candidates for the element.
let result = chooseFirstSelector(injectedScript, targetElement.ownerDocument, element, [...textCandidates, ...noTextCandidates], allowNthMatch, strict);
@ -144,7 +144,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
return calculateCached(targetElement, true);
}
function buildCandidates(element: Element, accessibleNameCache: Map<Element, boolean>): SelectorToken[] {
function buildCandidates(injectedScript: InjectedScript, element: Element, accessibleNameCache: Map<Element, boolean>): SelectorToken[] {
const candidates: SelectorToken[] = [];
if (element.getAttribute('data-testid'))
@ -155,10 +155,15 @@ function buildCandidates(element: Element, accessibleNameCache: Map<Element, boo
candidates.push({ engine: 'css', selector: `[${attr}=${quoteAttributeValue(element.getAttribute(attr)!)}]`, score: 2 });
}
if (element.nodeName === 'INPUT') {
const input = element as HTMLInputElement;
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 });
const label = input.labels?.[0];
if (label) {
const labelText = elementText(injectedScript._evaluator._cacheText, label).full.trim();
candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(labelText, false, true), score: 3 });
}
}
const ariaRole = getAriaRole(element);

View File

@ -259,6 +259,11 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
}
function toCallWithExact(method: string, body: string, exact: boolean) {
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) {
const regex = body.substring(1, body.lastIndexOf('/'));
const suffix = body.endsWith('i') ? ', RegexOptions.IgnoreCase' : '';
return `${method}(new Regex(${quote(regex)}${suffix}))`;
}
if (exact)
return `${method}(${quote(body)}, new () { Exact: true })`;
return `${method}(${quote(body)})`;

View File

@ -203,6 +203,11 @@ export class JavaLanguageGenerator implements LanguageGenerator {
}
function toCallWithExact(clazz: string, method: string, body: string, exact: boolean) {
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) {
const regex = body.substring(1, body.lastIndexOf('/'));
const suffix = body.endsWith('i') ? ', Pattern.CASE_INSENSITIVE' : '';
return `${method}(Pattern.compile(${quote(regex)}${suffix}))`;
}
if (exact)
return `${method}(${quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(exact))`;
return `${method}(${quote(body)})`;

View File

@ -240,6 +240,8 @@ ${useText ? '\ntest.use(' + useText + ');\n' : ''}
}
function toCallWithExact(method: string, body: string, exact: boolean) {
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i')))
return `${method}(${body})`;
return exact ? `${method}(${quote(body)}, { exact: true })` : `${method}(${quote(body)})`;
}

View File

@ -104,6 +104,11 @@ export function asLocator(generator: LanguageGenerator, selector: string, isFram
tokens.push(generator.generateLocator(base, 'text', text, { exact }));
continue;
}
if (part.name === 'internal:label') {
const { exact, text } = detectExact(part.body as string);
tokens.push(generator.generateLocator(base, 'label', text, { exact }));
continue;
}
if (part.name === 'role') {
const attrSelector = parseAttributeSelector(part.body as string, true);
const attrs: Record<string, boolean | string> = {};

View File

@ -258,6 +258,11 @@ with sync_playwright() as playwright:
}
function toCallWithExact(method: string, body: string, exact: boolean) {
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) {
const regex = body.substring(1, body.lastIndexOf('/'));
const suffix = body.endsWith('i') ? ', re.IGNORECASE' : '';
return `${method}(re.compile(r${quote(regex)}${suffix}))`;
}
if (exact)
return `${method}(${quote(body)}, exact=true)`;
return `${method}(${quote(body)})`;

View File

@ -319,4 +319,61 @@ test.describe('cli codegen', () => {
await page.GetByAltText("Country").ClickAsync();`);
});
test('should generate getByLabel', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<label for=target>Country</label><input id=target>`);
const selector = await recorder.hoverOverElement('input');
expect(selector).toBe('internal:label=Country');
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
page.dispatchEvent('input', 'click', { detail: 1 })
]);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.getByLabel('Country').click();`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_label("Country").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_label("Country").click()`);
expect.soft(sources.get('Java').text).toContain(`
page.getByLabel("Country").click()`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByLabel("Country").ClickAsync();`);
});
test('should generate getByLabel with regex', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<label for=target>Coun"try</label><input id=target>`);
const selector = await recorder.hoverOverElement('input');
expect(selector).toBe('internal:label=/Coun"try/');
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
page.dispatchEvent('input', 'click', { detail: 1 })
]);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.getByLabel(/Coun"try/).click();`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_label(re.compile(r"Coun\\\"try")).click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_label(re.compile(r"Coun\\\"try")).click()`);
expect.soft(sources.get('Java').text).toContain(`
page.getByLabel(Pattern.compile("Coun\\\"try")).click()`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByLabel(new Regex("Coun\\\"try")).ClickAsync();`);
});
});

View File

@ -366,4 +366,11 @@ it.describe('selector generator', () => {
expect(await generate(page, 'button')).toBe('[data-test-id="testId"]');
});
it('should generate label selector', async ({ page }) => {
await page.setContent(`<label for=target>Country</label><input id=target>`);
expect(await generate(page, 'input')).toBe('internal:label=Country');
await page.setContent(`<label for=target>Coun"try</label><input id=target>`);
expect(await generate(page, 'input')).toBe('internal:label=/Coun"try/');
});
});