cherry-pick(#18010): fix(generator): generate nice locators for arbitrary selectors
This commit is contained in:
parent
3367ebd968
commit
04b3b7190c
|
|
@ -158,7 +158,7 @@ function buildCandidates(injectedScript: InjectedScript, element: Element, acces
|
||||||
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
||||||
const input = element as HTMLInputElement | HTMLTextAreaElement;
|
const input = element as HTMLInputElement | HTMLTextAreaElement;
|
||||||
if (input.placeholder)
|
if (input.placeholder)
|
||||||
candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder, true)}]`, score: 3 });
|
candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder, false)}]`, score: 3 });
|
||||||
const label = input.labels?.[0];
|
const label = input.labels?.[0];
|
||||||
if (label) {
|
if (label) {
|
||||||
const labelText = elementText(injectedScript._evaluator._cacheText, label).full.trim();
|
const labelText = elementText(injectedScript._evaluator._cacheText, label).full.trim();
|
||||||
|
|
@ -176,7 +176,7 @@ function buildCandidates(injectedScript: InjectedScript, element: Element, acces
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.getAttribute('alt') && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName))
|
if (element.getAttribute('alt') && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName))
|
||||||
candidates.push({ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(element.getAttribute('alt')!, true)}]`, score: 10 });
|
candidates.push({ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(element.getAttribute('alt')!, false)}]`, score: 10 });
|
||||||
|
|
||||||
if (element.getAttribute('name') && ['BUTTON', 'FORM', 'FIELDSET', 'FRAME', 'IFRAME', 'INPUT', 'KEYGEN', 'OBJECT', 'OUTPUT', 'SELECT', 'TEXTAREA', 'MAP', 'META', 'PARAM'].includes(element.nodeName))
|
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 });
|
candidates.push({ engine: 'css', selector: `${cssEscape(element.nodeName.toLowerCase())}[name=${quoteAttributeValue(element.getAttribute('name')!)}]`, score: 50 });
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder'
|
||||||
export type LocatorBase = 'page' | 'locator' | 'frame-locator';
|
export type LocatorBase = 'page' | 'locator' | 'frame-locator';
|
||||||
|
|
||||||
export interface LocatorFactory {
|
export interface LocatorFactory {
|
||||||
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options?: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean }): string;
|
generateLocator(base: LocatorBase, kind: LocatorType, body: string | RegExp, options?: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean }): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function asLocator(lang: Language, selector: string, isFrameLocator: boolean = false): string {
|
export function asLocator(lang: Language, selector: string, isFrameLocator: boolean = false): string {
|
||||||
|
|
@ -74,13 +74,14 @@ function innerAsLocator(factory: LocatorFactory, selector: string, isFrameLocato
|
||||||
|
|
||||||
if (part.name === 'internal:attr') {
|
if (part.name === 'internal:attr') {
|
||||||
const attrSelector = parseAttributeSelector(part.body as string, true);
|
const attrSelector = parseAttributeSelector(part.body as string, true);
|
||||||
const { name, value } = attrSelector.attributes[0];
|
const { name, value, caseSensitive } = attrSelector.attributes[0];
|
||||||
if (name === 'data-testid') {
|
if (name === 'data-testid') {
|
||||||
tokens.push(factory.generateLocator(base, 'test-id', value));
|
tokens.push(factory.generateLocator(base, 'test-id', value));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { exact, text } = detectExact(value);
|
const text = value as string | RegExp;
|
||||||
|
const exact = !!caseSensitive;
|
||||||
if (name === 'placeholder') {
|
if (name === 'placeholder') {
|
||||||
tokens.push(factory.generateLocator(base, 'placeholder', text, { exact }));
|
tokens.push(factory.generateLocator(base, 'placeholder', text, { exact }));
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -104,8 +105,11 @@ function innerAsLocator(factory: LocatorFactory, selector: string, isFrameLocato
|
||||||
return tokens.join('.');
|
return tokens.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectExact(text: string): { exact: boolean, text: string } {
|
function detectExact(text: string): { exact?: boolean, text: string | RegExp } {
|
||||||
let exact = false;
|
let exact = false;
|
||||||
|
const match = text.match(/^\/(.*)\/([igm]*)$/);
|
||||||
|
if (match)
|
||||||
|
return { text: new RegExp(match[1], match[2]) };
|
||||||
if (text.startsWith('"') && text.endsWith('"')) {
|
if (text.startsWith('"') && text.endsWith('"')) {
|
||||||
text = JSON.parse(text);
|
text = JSON.parse(text);
|
||||||
exact = true;
|
exact = true;
|
||||||
|
|
@ -114,10 +118,10 @@ function detectExact(text: string): { exact: boolean, text: string } {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JavaScriptLocatorFactory implements LocatorFactory {
|
export class JavaScriptLocatorFactory implements LocatorFactory {
|
||||||
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
generateLocator(base: LocatorBase, kind: LocatorType, body: string | RegExp, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'default':
|
case 'default':
|
||||||
return `locator(${this.quote(body)})`;
|
return `locator(${this.quote(body as string)})`;
|
||||||
case 'nth':
|
case 'nth':
|
||||||
return `nth(${body})`;
|
return `nth(${body})`;
|
||||||
case 'first':
|
case 'first':
|
||||||
|
|
@ -129,11 +133,11 @@ export class JavaScriptLocatorFactory implements LocatorFactory {
|
||||||
for (const [name, value] of Object.entries(options.attrs!))
|
for (const [name, value] of Object.entries(options.attrs!))
|
||||||
attrs.push(`${name}: ${typeof value === 'string' ? this.quote(value) : value}`);
|
attrs.push(`${name}: ${typeof value === 'string' ? this.quote(value) : value}`);
|
||||||
const attrString = attrs.length ? `, { ${attrs.join(', ')} }` : '';
|
const attrString = attrs.length ? `, { ${attrs.join(', ')} }` : '';
|
||||||
return `getByRole(${this.quote(body)}${attrString})`;
|
return `getByRole(${this.quote(body as string)}${attrString})`;
|
||||||
case 'has-text':
|
case 'has-text':
|
||||||
return `locator(${this.quote(body)}, { hasText: ${this.quote(options.hasText!)} })`;
|
return `locator(${this.quote(body as string)}, { hasText: ${this.quote(options.hasText!)} })`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
return `getByTestId(${this.quote(body)})`;
|
return `getByTestId(${this.quote(body as string)})`;
|
||||||
case 'text':
|
case 'text':
|
||||||
return this.toCallWithExact('getByText', body, !!options.exact);
|
return this.toCallWithExact('getByText', body, !!options.exact);
|
||||||
case 'alt':
|
case 'alt':
|
||||||
|
|
@ -149,8 +153,8 @@ export class JavaScriptLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private toCallWithExact(method: string, body: string, exact: boolean) {
|
private toCallWithExact(method: string, body: string | RegExp, exact?: boolean) {
|
||||||
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i')))
|
if (isRegExp(body))
|
||||||
return `${method}(${body})`;
|
return `${method}(${body})`;
|
||||||
return exact ? `${method}(${this.quote(body)}, { exact: true })` : `${method}(${this.quote(body)})`;
|
return exact ? `${method}(${this.quote(body)}, { exact: true })` : `${method}(${this.quote(body)})`;
|
||||||
}
|
}
|
||||||
|
|
@ -161,10 +165,10 @@ export class JavaScriptLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PythonLocatorFactory implements LocatorFactory {
|
export class PythonLocatorFactory implements LocatorFactory {
|
||||||
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
generateLocator(base: LocatorBase, kind: LocatorType, body: string | RegExp, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'default':
|
case 'default':
|
||||||
return `locator(${this.quote(body)})`;
|
return `locator(${this.quote(body as string)})`;
|
||||||
case 'nth':
|
case 'nth':
|
||||||
return `nth(${body})`;
|
return `nth(${body})`;
|
||||||
case 'first':
|
case 'first':
|
||||||
|
|
@ -176,11 +180,11 @@ export class PythonLocatorFactory implements LocatorFactory {
|
||||||
for (const [name, value] of Object.entries(options.attrs!))
|
for (const [name, value] of Object.entries(options.attrs!))
|
||||||
attrs.push(`${toSnakeCase(name)}=${typeof value === 'string' ? this.quote(value) : value}`);
|
attrs.push(`${toSnakeCase(name)}=${typeof value === 'string' ? this.quote(value) : value}`);
|
||||||
const attrString = attrs.length ? `, ${attrs.join(', ')}` : '';
|
const attrString = attrs.length ? `, ${attrs.join(', ')}` : '';
|
||||||
return `get_by_role(${this.quote(body)}${attrString})`;
|
return `get_by_role(${this.quote(body as string)}${attrString})`;
|
||||||
case 'has-text':
|
case 'has-text':
|
||||||
return `locator(${this.quote(body)}, has_text=${this.quote(options.hasText!)})`;
|
return `locator(${this.quote(body as string)}, has_text=${this.quote(options.hasText!)})`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
return `get_by_test_id(${this.quote(body)})`;
|
return `get_by_test_id(${this.quote(body as string)})`;
|
||||||
case 'text':
|
case 'text':
|
||||||
return this.toCallWithExact('get_by_text', body, !!options.exact);
|
return this.toCallWithExact('get_by_text', body, !!options.exact);
|
||||||
case 'alt':
|
case 'alt':
|
||||||
|
|
@ -196,11 +200,10 @@ export class PythonLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private toCallWithExact(method: string, body: string, exact: boolean) {
|
private toCallWithExact(method: string, body: string | RegExp, exact: boolean) {
|
||||||
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) {
|
if (isRegExp(body)) {
|
||||||
const regex = body.substring(1, body.lastIndexOf('/'));
|
const suffix = body.flags.includes('i') ? ', re.IGNORECASE' : '';
|
||||||
const suffix = body.endsWith('i') ? ', re.IGNORECASE' : '';
|
return `${method}(re.compile(r${this.quote(body.source)}${suffix}))`;
|
||||||
return `${method}(re.compile(r${this.quote(regex)}${suffix}))`;
|
|
||||||
}
|
}
|
||||||
if (exact)
|
if (exact)
|
||||||
return `${method}(${this.quote(body)}, exact=true)`;
|
return `${method}(${this.quote(body)}, exact=true)`;
|
||||||
|
|
@ -213,7 +216,7 @@ export class PythonLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JavaLocatorFactory implements LocatorFactory {
|
export class JavaLocatorFactory implements LocatorFactory {
|
||||||
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
generateLocator(base: LocatorBase, kind: LocatorType, body: string | RegExp, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
||||||
let clazz: string;
|
let clazz: string;
|
||||||
switch (base) {
|
switch (base) {
|
||||||
case 'page': clazz = 'Page'; break;
|
case 'page': clazz = 'Page'; break;
|
||||||
|
|
@ -222,7 +225,7 @@ export class JavaLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'default':
|
case 'default':
|
||||||
return `locator(${this.quote(body)})`;
|
return `locator(${this.quote(body as string)})`;
|
||||||
case 'nth':
|
case 'nth':
|
||||||
return `nth(${body})`;
|
return `nth(${body})`;
|
||||||
case 'first':
|
case 'first':
|
||||||
|
|
@ -234,11 +237,11 @@ export class JavaLocatorFactory implements LocatorFactory {
|
||||||
for (const [name, value] of Object.entries(options.attrs!))
|
for (const [name, value] of Object.entries(options.attrs!))
|
||||||
attrs.push(`.set${toTitleCase(name)}(${typeof value === 'string' ? this.quote(value) : value})`);
|
attrs.push(`.set${toTitleCase(name)}(${typeof value === 'string' ? this.quote(value) : value})`);
|
||||||
const attrString = attrs.length ? `, new ${clazz}.GetByRoleOptions()${attrs.join('')}` : '';
|
const attrString = attrs.length ? `, new ${clazz}.GetByRoleOptions()${attrs.join('')}` : '';
|
||||||
return `getByRole(AriaRole.${toSnakeCase(body).toUpperCase()}${attrString})`;
|
return `getByRole(AriaRole.${toSnakeCase(body as string).toUpperCase()}${attrString})`;
|
||||||
case 'has-text':
|
case 'has-text':
|
||||||
return `locator(${this.quote(body)}, new ${clazz}.LocatorOptions().setHasText(${this.quote(options.hasText!)}))`;
|
return `locator(${this.quote(body as string)}, new ${clazz}.LocatorOptions().setHasText(${this.quote(options.hasText!)}))`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
return `getByTestId(${this.quote(body)})`;
|
return `getByTestId(${this.quote(body as string)})`;
|
||||||
case 'text':
|
case 'text':
|
||||||
return this.toCallWithExact(clazz, 'getByText', body, !!options.exact);
|
return this.toCallWithExact(clazz, 'getByText', body, !!options.exact);
|
||||||
case 'alt':
|
case 'alt':
|
||||||
|
|
@ -254,11 +257,10 @@ export class JavaLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private toCallWithExact(clazz: string, method: string, body: string, exact: boolean) {
|
private toCallWithExact(clazz: string, method: string, body: string | RegExp, exact: boolean) {
|
||||||
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) {
|
if (isRegExp(body)) {
|
||||||
const regex = body.substring(1, body.lastIndexOf('/'));
|
const suffix = body.flags.includes('i') ? ', Pattern.CASE_INSENSITIVE' : '';
|
||||||
const suffix = body.endsWith('i') ? ', Pattern.CASE_INSENSITIVE' : '';
|
return `${method}(Pattern.compile(${this.quote(body.source)}${suffix}))`;
|
||||||
return `${method}(Pattern.compile(${this.quote(regex)}${suffix}))`;
|
|
||||||
}
|
}
|
||||||
if (exact)
|
if (exact)
|
||||||
return `${method}(${this.quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(exact))`;
|
return `${method}(${this.quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(exact))`;
|
||||||
|
|
@ -271,10 +273,10 @@ export class JavaLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CSharpLocatorFactory implements LocatorFactory {
|
export class CSharpLocatorFactory implements LocatorFactory {
|
||||||
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
generateLocator(base: LocatorBase, kind: LocatorType, body: string | RegExp, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'default':
|
case 'default':
|
||||||
return `Locator(${this.quote(body)})`;
|
return `Locator(${this.quote(body as string)})`;
|
||||||
case 'nth':
|
case 'nth':
|
||||||
return `Nth(${body})`;
|
return `Nth(${body})`;
|
||||||
case 'first':
|
case 'first':
|
||||||
|
|
@ -286,11 +288,11 @@ export class CSharpLocatorFactory implements LocatorFactory {
|
||||||
for (const [name, value] of Object.entries(options.attrs!))
|
for (const [name, value] of Object.entries(options.attrs!))
|
||||||
attrs.push(`${toTitleCase(name)} = ${typeof value === 'string' ? this.quote(value) : value}`);
|
attrs.push(`${toTitleCase(name)} = ${typeof value === 'string' ? this.quote(value) : value}`);
|
||||||
const attrString = attrs.length ? `, new () { ${attrs.join(', ')} }` : '';
|
const attrString = attrs.length ? `, new () { ${attrs.join(', ')} }` : '';
|
||||||
return `GetByRole(AriaRole.${toTitleCase(body)}${attrString})`;
|
return `GetByRole(AriaRole.${toTitleCase(body as string)}${attrString})`;
|
||||||
case 'has-text':
|
case 'has-text':
|
||||||
return `Locator(${this.quote(body)}, new () { HasTextString: ${this.quote(options.hasText!)} })`;
|
return `Locator(${this.quote(body as string)}, new () { HasTextString: ${this.quote(options.hasText!)} })`;
|
||||||
case 'test-id':
|
case 'test-id':
|
||||||
return `GetByTestId(${this.quote(body)})`;
|
return `GetByTestId(${this.quote(body as string)})`;
|
||||||
case 'text':
|
case 'text':
|
||||||
return this.toCallWithExact('GetByText', body, !!options.exact);
|
return this.toCallWithExact('GetByText', body, !!options.exact);
|
||||||
case 'alt':
|
case 'alt':
|
||||||
|
|
@ -306,11 +308,10 @@ export class CSharpLocatorFactory implements LocatorFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private toCallWithExact(method: string, body: string, exact: boolean) {
|
private toCallWithExact(method: string, body: string | RegExp, exact: boolean) {
|
||||||
if (body.startsWith('/') && (body.endsWith('/') || body.endsWith('/i'))) {
|
if (isRegExp(body)) {
|
||||||
const regex = body.substring(1, body.lastIndexOf('/'));
|
const suffix = body.flags.includes('i') ? ', RegexOptions.IgnoreCase' : '';
|
||||||
const suffix = body.endsWith('i') ? ', RegexOptions.IgnoreCase' : '';
|
return `${method}(new Regex(${this.quote(body.source)}${suffix}))`;
|
||||||
return `${method}(new Regex(${this.quote(regex)}${suffix}))`;
|
|
||||||
}
|
}
|
||||||
if (exact)
|
if (exact)
|
||||||
return `${method}(${this.quote(body)}, new () { Exact: true })`;
|
return `${method}(${this.quote(body)}, new () { Exact: true })`;
|
||||||
|
|
@ -328,3 +329,7 @@ const generators: Record<Language, LocatorFactory> = {
|
||||||
java: new JavaLocatorFactory(),
|
java: new JavaLocatorFactory(),
|
||||||
csharp: new CSharpLocatorFactory(),
|
csharp: new CSharpLocatorFactory(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isRegExp(obj: any): obj is RegExp {
|
||||||
|
return obj instanceof RegExp;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ test.describe('cli codegen', () => {
|
||||||
await recorder.setContentAndWait(`<input placeholder="Country"></input>`);
|
await recorder.setContentAndWait(`<input placeholder="Country"></input>`);
|
||||||
|
|
||||||
const selector = await recorder.hoverOverElement('input');
|
const selector = await recorder.hoverOverElement('input');
|
||||||
expect(selector).toBe('internal:attr=[placeholder="Country"]');
|
expect(selector).toBe('internal:attr=[placeholder="Country"i]');
|
||||||
|
|
||||||
const [sources] = await Promise.all([
|
const [sources] = await Promise.all([
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
|
|
@ -296,7 +296,7 @@ test.describe('cli codegen', () => {
|
||||||
await recorder.setContentAndWait(`<input alt="Country"></input>`);
|
await recorder.setContentAndWait(`<input alt="Country"></input>`);
|
||||||
|
|
||||||
const selector = await recorder.hoverOverElement('input');
|
const selector = await recorder.hoverOverElement('input');
|
||||||
expect(selector).toBe('internal:attr=[alt="Country"]');
|
expect(selector).toBe('internal:attr=[alt="Country"i]');
|
||||||
|
|
||||||
const [sources] = await Promise.all([
|
const [sources] = await Promise.all([
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
|
|
|
||||||
138
tests/library/locator-generator.spec.ts
Normal file
138
tests/library/locator-generator.spec.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { contextTest as it, expect } from '../config/browserTest';
|
||||||
|
import { asLocator } from '../../packages/playwright-core/lib/server/isomorphic/locatorGenerators';
|
||||||
|
import type { Locator } from 'playwright-core';
|
||||||
|
|
||||||
|
function generate(locator: Locator) {
|
||||||
|
const result: any = {};
|
||||||
|
for (const lang of ['javascript', 'python', 'java', 'csharp'])
|
||||||
|
result[lang] = asLocator(lang, (locator as any)._selector, false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('reverse engineer locators', async ({ page }) => {
|
||||||
|
expect.soft(generate(page.getByTestId('Hello'))).toEqual({
|
||||||
|
javascript: "getByTestId('Hello')",
|
||||||
|
python: 'get_by_test_id("Hello")',
|
||||||
|
java: 'getByTestId("Hello")',
|
||||||
|
csharp: 'GetByTestId("Hello")'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.soft(generate(page.getByTestId('He"llo'))).toEqual({
|
||||||
|
javascript: 'getByTestId(\'He"llo\')',
|
||||||
|
python: 'get_by_test_id("He\\\"llo")',
|
||||||
|
java: 'getByTestId("He\\\"llo")',
|
||||||
|
csharp: 'GetByTestId("He\\\"llo")'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.soft(generate(page.getByText('Hello', { exact: true }))).toEqual({
|
||||||
|
csharp: 'GetByText("Hello", new () { Exact: true })',
|
||||||
|
java: 'getByText("Hello", new Page.GetByTextOptions().setExact(exact))',
|
||||||
|
javascript: 'getByText(\'Hello\', { exact: true })',
|
||||||
|
python: 'get_by_text("Hello", exact=true)',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.soft(generate(page.getByText('Hello'))).toEqual({
|
||||||
|
csharp: 'GetByText("Hello")',
|
||||||
|
java: 'getByText("Hello")',
|
||||||
|
javascript: 'getByText(\'Hello\')',
|
||||||
|
python: 'get_by_text("Hello")',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByText(/Hello/))).toEqual({
|
||||||
|
csharp: 'GetByText(new Regex("Hello"))',
|
||||||
|
java: 'getByText(Pattern.compile("Hello"))',
|
||||||
|
javascript: 'getByText(/Hello/)',
|
||||||
|
python: 'get_by_text(re.compile(r"Hello"))',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByLabel('Name'))).toEqual({
|
||||||
|
csharp: 'GetByLabel("Name")',
|
||||||
|
java: 'getByLabel("Name")',
|
||||||
|
javascript: 'getByLabel(\'Name\')',
|
||||||
|
python: 'get_by_label("Name")',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByLabel('Last Name', { exact: true }))).toEqual({
|
||||||
|
csharp: 'GetByLabel("Last Name", new () { Exact: true })',
|
||||||
|
java: 'getByLabel("Last Name", new Page.GetByLabelOptions().setExact(exact))',
|
||||||
|
javascript: 'getByLabel(\'Last Name\', { exact: true })',
|
||||||
|
python: 'get_by_label("Last Name", exact=true)',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByLabel(/Last\s+name/i))).toEqual({
|
||||||
|
csharp: 'GetByLabel(new Regex("Last\\\\s+name", RegexOptions.IgnoreCase))',
|
||||||
|
java: 'getByLabel(Pattern.compile("Last\\\\s+name", Pattern.CASE_INSENSITIVE))',
|
||||||
|
javascript: 'getByLabel(/Last\\s+name/i)',
|
||||||
|
python: 'get_by_label(re.compile(r"Last\\\\s+name", re.IGNORECASE))',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.soft(generate(page.getByPlaceholder('hello'))).toEqual({
|
||||||
|
csharp: 'GetByPlaceholder("hello")',
|
||||||
|
java: 'getByPlaceholder("hello")',
|
||||||
|
javascript: 'getByPlaceholder(\'hello\')',
|
||||||
|
python: 'get_by_placeholder("hello")',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByPlaceholder('Hello', { exact: true }))).toEqual({
|
||||||
|
csharp: 'GetByPlaceholder("Hello", new () { Exact: true })',
|
||||||
|
java: 'getByPlaceholder("Hello", new Page.GetByPlaceholderOptions().setExact(exact))',
|
||||||
|
javascript: 'getByPlaceholder(\'Hello\', { exact: true })',
|
||||||
|
python: 'get_by_placeholder("Hello", exact=true)',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByPlaceholder(/wor/i))).toEqual({
|
||||||
|
csharp: 'GetByPlaceholder(new Regex("wor", RegexOptions.IgnoreCase))',
|
||||||
|
java: 'getByPlaceholder(Pattern.compile("wor", Pattern.CASE_INSENSITIVE))',
|
||||||
|
javascript: 'getByPlaceholder(/wor/i)',
|
||||||
|
python: 'get_by_placeholder(re.compile(r"wor", re.IGNORECASE))',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.soft(generate(page.getByAltText('hello'))).toEqual({
|
||||||
|
csharp: 'GetByAltText("hello")',
|
||||||
|
java: 'getByAltText("hello")',
|
||||||
|
javascript: 'getByAltText(\'hello\')',
|
||||||
|
python: 'get_by_alt_text("hello")',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByAltText('Hello', { exact: true }))).toEqual({
|
||||||
|
csharp: 'GetByAltText("Hello", new () { Exact: true })',
|
||||||
|
java: 'getByAltText("Hello", new Page.GetByAltTextOptions().setExact(exact))',
|
||||||
|
javascript: 'getByAltText(\'Hello\', { exact: true })',
|
||||||
|
python: 'get_by_alt_text("Hello", exact=true)',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByAltText(/wor/i))).toEqual({
|
||||||
|
csharp: 'GetByAltText(new Regex("wor", RegexOptions.IgnoreCase))',
|
||||||
|
java: 'getByAltText(Pattern.compile("wor", Pattern.CASE_INSENSITIVE))',
|
||||||
|
javascript: 'getByAltText(/wor/i)',
|
||||||
|
python: 'get_by_alt_text(re.compile(r"wor", re.IGNORECASE))',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect.soft(generate(page.getByTitle('hello'))).toEqual({
|
||||||
|
csharp: 'GetByTitle("hello")',
|
||||||
|
java: 'getByTitle("hello")',
|
||||||
|
javascript: 'getByTitle(\'hello\')',
|
||||||
|
python: 'get_by_title("hello")',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByTitle('Hello', { exact: true }))).toEqual({
|
||||||
|
csharp: 'GetByTitle("Hello", new () { Exact: true })',
|
||||||
|
java: 'getByTitle("Hello", new Page.GetByTitleOptions().setExact(exact))',
|
||||||
|
javascript: 'getByTitle(\'Hello\', { exact: true })',
|
||||||
|
python: 'get_by_title("Hello", exact=true)',
|
||||||
|
});
|
||||||
|
expect.soft(generate(page.getByTitle(/wor/i))).toEqual({
|
||||||
|
csharp: 'GetByTitle(new Regex("wor", RegexOptions.IgnoreCase))',
|
||||||
|
java: 'getByTitle(Pattern.compile("wor", Pattern.CASE_INSENSITIVE))',
|
||||||
|
javascript: 'getByTitle(/wor/i)',
|
||||||
|
python: 'get_by_title(re.compile(r"wor", re.IGNORECASE))',
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -45,7 +45,7 @@ it.describe('selector generator', () => {
|
||||||
|
|
||||||
it('should not escape spaces inside named attr selectors', async ({ page }) => {
|
it('should not escape spaces inside named attr selectors', async ({ page }) => {
|
||||||
await page.setContent(`<input placeholder="Foo b ar"/>`);
|
await page.setContent(`<input placeholder="Foo b ar"/>`);
|
||||||
expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"Foo b ar\"]');
|
expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"Foo b ar\"i]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate text for <input type=button>', async ({ page }) => {
|
it('should generate text for <input type=button>', async ({ page }) => {
|
||||||
|
|
@ -232,7 +232,7 @@ it.describe('selector generator', () => {
|
||||||
});
|
});
|
||||||
it('placeholder', async ({ page }) => {
|
it('placeholder', async ({ page }) => {
|
||||||
await page.setContent(`<input placeholder="foobar" type="text"/>`);
|
await page.setContent(`<input placeholder="foobar" type="text"/>`);
|
||||||
expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"foobar\"]');
|
expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"foobar\"i]');
|
||||||
});
|
});
|
||||||
it('type', async ({ page }) => {
|
it('type', async ({ page }) => {
|
||||||
await page.setContent(`<input type="text"/>`);
|
await page.setContent(`<input type="text"/>`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue