chore: generate api calls (#17794)

This commit is contained in:
Pavel Feldman 2022-10-03 16:14:02 -08:00 committed by GitHub
parent ade75d66cf
commit 1311767f87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 535 additions and 175 deletions

View File

@ -1,5 +1,6 @@
[*]
../
../isomorphic/**
../registry/**
../../common/
../../protocol/

View File

@ -15,13 +15,14 @@
*/
import type { BrowserContextOptions } from '../../..';
import type { LanguageGenerator, LanguageGeneratorOptions } from './language';
import { asLocator } from './language';
import type { LanguageGenerator, LanguageGeneratorOptions, LocatorBase, LocatorType } from './language';
import { sanitizeDeviceOptions, toSignalMap } from './language';
import type { ActionInContext } from './codeGenerator';
import type { Action } from './recorderActions';
import type { MouseClickOptions } from './utils';
import { toModifiers } from './utils';
import { escapeWithQuotes } from '../../utils/isomorphic/stringUtils';
import { escapeWithQuotes, toTitleCase } from '../../utils/isomorphic/stringUtils';
import deviceDescriptors from '../deviceDescriptors';
type CSharpLanguageMode = 'library' | 'mstest' | 'nunit';
@ -76,7 +77,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'FrameLocator'));
const locators = actionInContext.frame.selectorsChain.map(selector => `.FrameLocator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.Frame(${quote(actionInContext.frame.name)})`;
@ -139,30 +140,34 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
if (action.position)
options.position = action.position;
if (!Object.entries(options).length)
return asLocator(action.selector) + `.${method}Async()`;
return this._asLocator(action.selector) + `.${method}Async()`;
const optionsString = formatObject(options, ' ', 'Locator' + method + 'Options');
return asLocator(action.selector) + `.${method}Async(${optionsString})`;
return this._asLocator(action.selector) + `.${method}Async(${optionsString})`;
}
case 'check':
return asLocator(action.selector) + `.CheckAsync()`;
return this._asLocator(action.selector) + `.CheckAsync()`;
case 'uncheck':
return asLocator(action.selector) + `.UncheckAsync()`;
return this._asLocator(action.selector) + `.UncheckAsync()`;
case 'fill':
return asLocator(action.selector) + `.FillAsync(${quote(action.text)})`;
return this._asLocator(action.selector) + `.FillAsync(${quote(action.text)})`;
case 'setInputFiles':
return asLocator(action.selector) + `.SetInputFilesAsync(${formatObject(action.files)})`;
return this._asLocator(action.selector) + `.SetInputFilesAsync(${formatObject(action.files)})`;
case 'press': {
const modifiers = toModifiers(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return asLocator(action.selector) + `.PressAsync(${quote(shortcut)})`;
return this._asLocator(action.selector) + `.PressAsync(${quote(shortcut)})`;
}
case 'navigate':
return `GotoAsync(${quote(action.url)})`;
case 'select':
return asLocator(action.selector) + `.SelectOptionAsync(${formatObject(action.options)})`;
return this._asLocator(action.selector) + `.SelectOptionAsync(${formatObject(action.options)})`;
}
}
private _asLocator(selector: string) {
return asLocator(this, selector);
}
generateHeader(options: LanguageGeneratorOptions): string {
if (this._mode === 'library')
return this.generateStandaloneHeader(options);
@ -216,6 +221,47 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
return `${storageStateLine} }
}\n`;
}
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
switch (kind) {
case 'default':
return `Locator(${quote(body)})`;
case 'nth':
return `Nth(${body})`;
case 'first':
return `First`;
case 'last':
return `Last`;
case 'role':
const attrs: string[] = [];
for (const [name, value] of Object.entries(options.attrs!))
attrs.push(`${toTitleCase(name)} = ${typeof value === 'string' ? quote(value) : value}`);
const attrString = attrs.length ? `, new () { ${attrs.join(', ')} }` : '';
return `GetByRole(${quote(body)}${attrString})`;
case 'has-text':
return `Locator(${quote(body)}, new () { HasTextString: ${quote(options.hasText!)} })`;
case 'test-id':
return `GetByTestId(${quote(body)})`;
case 'text':
return toCallWithExact('GetByText', body, !!options.exact);
case 'alt':
return toCallWithExact('GetByAltText', body, !!options.exact);
case 'placeholder':
return toCallWithExact('GetByPlaceholderText', body, !!options.exact);
case 'label':
return toCallWithExact('GetByLabelText', body, !!options.exact);
case 'title':
return toCallWithExact('GetByTitle', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
}
function toCallWithExact(method: string, body: string, exact: boolean) {
if (exact)
return `${method}(${quote(body)}, new () { Exact: true })`;
return `${method}(${quote(body)})`;
}
function formatObject(value: any, indent = ' ', name = ''): string {
@ -347,12 +393,3 @@ class CSharpFormatter {
function quote(text: string) {
return escapeWithQuotes(text, '\"');
}
function asLocator(selector: string, locatorFn = 'Locator') {
const match = selector.match(/(.*)\s+>>\s+nth=(\d+)$/);
if (!match)
return `${locatorFn}(${quote(selector)})`;
if (+match[2] === 0)
return `${locatorFn}(${quote(match[1])}).First`;
return `${locatorFn}(${quote(match[1])}).Nth(${match[2]})`;
}

View File

@ -15,7 +15,8 @@
*/
import type { BrowserContextOptions } from '../../..';
import type { LanguageGenerator, LanguageGeneratorOptions } from './language';
import { asLocator } from './language';
import type { LanguageGenerator, LanguageGeneratorOptions, LocatorBase, LocatorType } from './language';
import { toSignalMap } from './language';
import type { ActionInContext } from './codeGenerator';
import type { Action } from './recorderActions';
@ -23,7 +24,7 @@ import type { MouseClickOptions } from './utils';
import { toModifiers } from './utils';
import deviceDescriptors from '../deviceDescriptors';
import { JavaScriptFormatter } from './javascript';
import { escapeWithQuotes } from '../../utils/isomorphic/stringUtils';
import { escapeWithQuotes, toTitleCase } from '../../utils/isomorphic/stringUtils';
export class JavaLanguageGenerator implements LanguageGenerator {
id = 'java';
@ -45,11 +46,13 @@ export class JavaLanguageGenerator implements LanguageGenerator {
}
let subject: string;
let inFrameLocator = false;
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'frameLocator'));
const locators = actionInContext.frame.selectorsChain.map(selector => `.frameLocator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
inFrameLocator = true;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.frame(${quote(actionInContext.frame.name)})`;
} else {
@ -65,7 +68,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
});`);
}
const actionCall = this._generateActionCall(action);
const actionCall = this._generateActionCall(action, inFrameLocator);
let code = `${subject}.${actionCall};`;
if (signals.popup) {
@ -87,7 +90,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
return formatter.format();
}
private _generateActionCall(action: Action): string {
private _generateActionCall(action: Action, inFrameLocator: boolean): string {
switch (action.name) {
case 'openPage':
throw Error('Not reached');
@ -108,28 +111,32 @@ export class JavaLanguageGenerator implements LanguageGenerator {
if (action.position)
options.position = action.position;
const optionsText = formatClickOptions(options);
return asLocator(action.selector) + `.${method}(${optionsText})`;
return this._asLocator(action.selector, inFrameLocator) + `.${method}(${optionsText})`;
}
case 'check':
return asLocator(action.selector) + `.check()`;
return this._asLocator(action.selector, inFrameLocator) + `.check()`;
case 'uncheck':
return asLocator(action.selector) + `.uncheck()`;
return this._asLocator(action.selector, inFrameLocator) + `.uncheck()`;
case 'fill':
return asLocator(action.selector) + `.fill(${quote(action.text)})`;
return this._asLocator(action.selector, inFrameLocator) + `.fill(${quote(action.text)})`;
case 'setInputFiles':
return asLocator(action.selector) + `.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)})`;
return this._asLocator(action.selector, inFrameLocator) + `.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press': {
const modifiers = toModifiers(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return asLocator(action.selector) + `.press(${quote(shortcut)})`;
return this._asLocator(action.selector, inFrameLocator) + `.press(${quote(shortcut)})`;
}
case 'navigate':
return `navigate(${quote(action.url)})`;
case 'select':
return asLocator(action.selector) + `.selectOption(${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])})`;
return this._asLocator(action.selector, inFrameLocator) + `.selectOption(${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])})`;
}
}
private _asLocator(selector: string, inFrameLocator: boolean) {
return asLocator(this, selector, inFrameLocator);
}
generateHeader(options: LanguageGeneratorOptions): string {
const formatter = new JavaScriptFormatter();
formatter.add(`
@ -152,6 +159,53 @@ export class JavaLanguageGenerator implements LanguageGenerator {
}
}`;
}
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
let clazz: string;
switch (base) {
case 'page': clazz = 'Page'; break;
case 'frame-locator': clazz = 'FrameLocator'; break;
case 'locator': clazz = 'Locator'; break;
}
switch (kind) {
case 'default':
return `locator(${quote(body)})`;
case 'nth':
return `nth(${body})`;
case 'first':
return `first()`;
case 'last':
return `last()`;
case 'role':
const attrs: string[] = [];
for (const [name, value] of Object.entries(options.attrs!))
attrs.push(`.set${toTitleCase(name)}(${typeof value === 'string' ? quote(value) : value})`);
const attrString = attrs.length ? `, new ${clazz}.GetByRoleOptions()${attrs.join('')}` : '';
return `getByRole(${quote(body)}${attrString})`;
case 'has-text':
return `locator(${quote(body)}, new ${clazz}.LocatorOptions().setHasText(${quote(options.hasText!)}))`;
case 'test-id':
return `getByTestId(${quote(body)})`;
case 'text':
return toCallWithExact(clazz, 'getByText', body, !!options.exact);
case 'alt':
return toCallWithExact(clazz, 'getByAltText', body, !!options.exact);
case 'placeholder':
return toCallWithExact(clazz, 'getByPlaceholderText', body, !!options.exact);
case 'label':
return toCallWithExact(clazz, 'getByLabelText', body, !!options.exact);
case 'title':
return toCallWithExact(clazz, 'getByTitle', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
}
function toCallWithExact(clazz: string, method: string, body: string, exact: boolean) {
if (exact)
return `${method}(${quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(exact))`;
return `${method}(${quote(body)})`;
}
function formatPath(files: string | string[]): string {
@ -251,12 +305,3 @@ function formatClickOptions(options: MouseClickOptions) {
function quote(text: string) {
return escapeWithQuotes(text, '\"');
}
function asLocator(selector: string, locatorFn = 'locator') {
const match = selector.match(/(.*)\s+>>\s+nth=(\d+)$/);
if (!match)
return `${locatorFn}(${quote(selector)})`;
if (+match[2] === 0)
return `${locatorFn}(${quote(match[1])}).first()`;
return `${locatorFn}(${quote(match[1])}).nth(${match[2]})`;
}

View File

@ -15,7 +15,8 @@
*/
import type { BrowserContextOptions } from '../../..';
import type { LanguageGenerator, LanguageGeneratorOptions } from './language';
import { asLocator } from './language';
import type { LanguageGenerator, LanguageGeneratorOptions, LocatorBase, LocatorType } from './language';
import { sanitizeDeviceOptions, toSignalMap } from './language';
import type { ActionInContext } from './codeGenerator';
import type { Action } from './recorderActions';
@ -57,7 +58,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'frameLocator'));
const locators = actionInContext.frame.selectorsChain.map(selector => `.frameLocator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.frame(${formatObject({ name: actionInContext.frame.name })})`;
@ -131,28 +132,32 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
if (action.position)
options.position = action.position;
const optionsString = formatOptions(options, false);
return asLocator(action.selector) + `.${method}(${optionsString})`;
return this._asLocator(action.selector) + `.${method}(${optionsString})`;
}
case 'check':
return asLocator(action.selector) + `.check()`;
return this._asLocator(action.selector) + `.check()`;
case 'uncheck':
return asLocator(action.selector) + `.uncheck()`;
return this._asLocator(action.selector) + `.uncheck()`;
case 'fill':
return asLocator(action.selector) + `.fill(${quote(action.text)})`;
return this._asLocator(action.selector) + `.fill(${quote(action.text)})`;
case 'setInputFiles':
return asLocator(action.selector) + `.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)})`;
return this._asLocator(action.selector) + `.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press': {
const modifiers = toModifiers(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return asLocator(action.selector) + `.press(${quote(shortcut)})`;
return this._asLocator(action.selector) + `.press(${quote(shortcut)})`;
}
case 'navigate':
return `goto(${quote(action.url)})`;
case 'select':
return asLocator(action.selector) + `.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])})`;
return this._asLocator(action.selector) + `.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])})`;
}
}
private _asLocator(selector: string) {
return asLocator(this, selector);
}
generateHeader(options: LanguageGeneratorOptions): string {
if (this._isTest)
return this.generateTestHeader(options);
@ -197,15 +202,45 @@ ${useText ? '\ntest.use(' + useText + ');\n' : ''}
await browser.close();
})();`;
}
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
switch (kind) {
case 'default':
return `locator(${quote(body)})`;
case 'nth':
return `nth(${body})`;
case 'first':
return `first()`;
case 'last':
return `last()`;
case 'role':
const attrs: string[] = [];
for (const [name, value] of Object.entries(options.attrs!))
attrs.push(`${name}: ${typeof value === 'string' ? quote(value) : value}`);
const attrString = attrs.length ? `, { ${attrs.join(', ')} }` : '';
return `getByRole(${quote(body)}${attrString})`;
case 'has-text':
return `locator(${quote(body)}, { hasText: ${quote(options.hasText!)} })`;
case 'test-id':
return `getByTestId(${quote(body)})`;
case 'text':
return toCallWithExact('getByText', body, !!options.exact);
case 'alt':
return toCallWithExact('getByAltText', body, !!options.exact);
case 'placeholder':
return toCallWithExact('getByPlaceholderText', body, !!options.exact);
case 'label':
return toCallWithExact('getByLabelText', body, !!options.exact);
case 'title':
return toCallWithExact('getByTitle', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
}
function asLocator(selector: string, locatorFn = 'locator') {
const match = selector.match(/(.*)\s+>>\s+nth=(\d+)$/);
if (!match)
return `${locatorFn}(${quote(selector)})`;
if (+match[2] === 0)
return `${locatorFn}(${quote(match[1])}).first()`;
return `${locatorFn}(${quote(match[1])}).nth(${match[2]})`;
function toCallWithExact(method: string, body: string, exact: boolean) {
return exact ? `${method}(${quote(body)}, { exact: true })` : `${method}(${quote(body)})`;
}
function formatOptions(value: any, hasArguments: boolean): string {

View File

@ -15,6 +15,9 @@
*/
import type { BrowserContextOptions, LaunchOptions } from '../../..';
import type { CSSComplexSelectorList } from '../isomorphic/cssParser';
import { parseAttributeSelector, parseSelector, stringifySelector } from '../isomorphic/selectorParser';
import type { ParsedSelector } from '../isomorphic/selectorParser';
import type { ActionInContext } from './codeGenerator';
import type { Action, DialogSignal, DownloadSignal, NavigationSignal, PopupSignal } from './recorderActions';
@ -26,6 +29,9 @@ export type LanguageGeneratorOptions = {
saveStorage?: string;
};
export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text';
export type LocatorBase = 'page' | 'locator' | 'frame-locator';
export interface LanguageGenerator {
id: string;
groupName: string;
@ -34,6 +40,7 @@ export interface LanguageGenerator {
generateHeader(options: LanguageGeneratorOptions): string;
generateAction(actionInContext: ActionInContext): string;
generateFooter(saveStorage: string | undefined): string;
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options?: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean }): string;
}
export function sanitizeDeviceOptions(device: any, options: BrowserContextOptions): BrowserContextOptions {
@ -68,3 +75,80 @@ export function toSignalMap(action: Action) {
dialog,
};
}
function detectExact(text: string): { exact: boolean, text: string } {
let exact = false;
if (text.startsWith('"') && text.endsWith('"')) {
text = JSON.parse(text);
exact = true;
}
return { exact, text };
}
export function asLocator(generator: LanguageGenerator, selector: string, isFrameLocator: boolean = false): string {
const parsed = parseSelector(selector);
const tokens: string[] = [];
for (const part of parsed.parts) {
const base = part === parsed.parts[0] ? (isFrameLocator ? 'frame-locator' : 'page') : 'locator';
if (part.name === 'nth') {
if (part.body === '0')
tokens.push(generator.generateLocator(base, 'first', ''));
else if (part.body === '-1')
tokens.push(generator.generateLocator(base, 'last', ''));
else
tokens.push(generator.generateLocator(base, 'nth', part.body as string));
continue;
}
if (part.name === 'text') {
const { exact, text } = detectExact(part.body as string);
tokens.push(generator.generateLocator(base, 'text', text, { exact }));
continue;
}
if (part.name === 'role') {
const attrSelector = parseAttributeSelector(part.body as string, true);
const attrs: Record<string, boolean | string> = {};
for (const attr of attrSelector.attributes!)
attrs[attr.name === 'include-hidden' ? 'includeHidden' : attr.name] = attr.value;
tokens.push(generator.generateLocator(base, 'role', attrSelector.name, { attrs }));
continue;
}
if (part.name === 'css') {
const parsed = part.body as CSSComplexSelectorList;
if (parsed[0].simples.length === 1 && parsed[0].simples[0].selector.functions.length === 1 && parsed[0].simples[0].selector.functions[0].name === 'hasText') {
const hasText = parsed[0].simples[0].selector.functions[0].args[0] as string;
tokens.push(generator.generateLocator(base, 'has-text', parsed[0].simples[0].selector.css!, { hasText }));
continue;
}
}
if (part.name === 'attr') {
const attrSelector = parseAttributeSelector(part.body as string, true);
const { name, value } = attrSelector.attributes[0];
if (name === 'data-testid') {
tokens.push(generator.generateLocator(base, 'test-id', value));
continue;
}
const { exact, text } = detectExact(value);
if (name === 'placeholder') {
tokens.push(generator.generateLocator(base, 'placeholder', text, { exact }));
continue;
}
if (name === 'alt') {
tokens.push(generator.generateLocator(base, 'alt', text, { exact }));
continue;
}
if (name === 'title') {
tokens.push(generator.generateLocator(base, 'title', text, { exact }));
continue;
}
if (name === 'label') {
tokens.push(generator.generateLocator(base, 'label', text, { exact }));
continue;
}
}
const p: ParsedSelector = { parts: [part] };
tokens.push(generator.generateLocator(base, 'default', stringifySelector(p)));
}
return tokens.join('.');
}

View File

@ -15,7 +15,8 @@
*/
import type { BrowserContextOptions } from '../../..';
import type { LanguageGenerator, LanguageGeneratorOptions } from './language';
import { asLocator } from './language';
import type { LanguageGenerator, LanguageGeneratorOptions, LocatorBase, LocatorType } from './language';
import { sanitizeDeviceOptions, toSignalMap } from './language';
import type { ActionInContext } from './codeGenerator';
import type { Action } from './recorderActions';
@ -64,7 +65,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'frame_locator'));
const locators = actionInContext.frame.selectorsChain.map(selector => `.frame_locator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.frame(${formatOptions({ name: actionInContext.frame.name }, false)})`;
@ -126,28 +127,32 @@ export class PythonLanguageGenerator implements LanguageGenerator {
if (action.position)
options.position = action.position;
const optionsString = formatOptions(options, false);
return asLocator(action.selector) + `.${method}(${optionsString})`;
return this._asLocator(action.selector) + `.${method}(${optionsString})`;
}
case 'check':
return asLocator(action.selector) + `.check()`;
return this._asLocator(action.selector) + `.check()`;
case 'uncheck':
return asLocator(action.selector) + `.uncheck()`;
return this._asLocator(action.selector) + `.uncheck()`;
case 'fill':
return asLocator(action.selector) + `.fill(${quote(action.text)})`;
return this._asLocator(action.selector) + `.fill(${quote(action.text)})`;
case 'setInputFiles':
return asLocator(action.selector) + `.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
return this._asLocator(action.selector) + `.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press': {
const modifiers = toModifiers(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return asLocator(action.selector) + `.press(${quote(shortcut)})`;
return this._asLocator(action.selector) + `.press(${quote(shortcut)})`;
}
case 'navigate':
return `goto(${quote(action.url)})`;
case 'select':
return asLocator(action.selector) + `.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
return this._asLocator(action.selector) + `.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
}
}
private _asLocator(selector: string) {
return asLocator(this, selector);
}
generateHeader(options: LanguageGeneratorOptions): string {
const formatter = new PythonFormatter();
if (this._isPyTest) {
@ -215,6 +220,47 @@ with sync_playwright() as playwright:
`;
}
}
generateLocator(base: LocatorBase, kind: LocatorType, body: string, options: { attrs?: Record<string, string | boolean>, hasText?: string, exact?: boolean } = {}): string {
switch (kind) {
case 'default':
return `locator(${quote(body)})`;
case 'nth':
return `nth(${body})`;
case 'first':
return `first`;
case 'last':
return `last`;
case 'role':
const attrs: string[] = [];
for (const [name, value] of Object.entries(options.attrs!))
attrs.push(`${toSnakeCase(name)}=${typeof value === 'string' ? quote(value) : value}`);
const attrString = attrs.length ? `, ${attrs.join(', ')}` : '';
return `get_by_role(${quote(body)}${attrString})`;
case 'has-text':
return `locator(${quote(body)}, has_text=${quote(options.hasText!)})`;
case 'test-id':
return `get_by_test_id(${quote(body)})`;
case 'text':
return toCallWithExact('get_by_text', body, !!options.exact);
case 'alt':
return toCallWithExact('get_by_alt_text', body, !!options.exact);
case 'placeholder':
return toCallWithExact('get_by_placeholder_text', body, !!options.exact);
case 'label':
return toCallWithExact('get_by_label_text', body, !!options.exact);
case 'title':
return toCallWithExact('get_by_title', body, !!options.exact);
default:
throw new Error('Unknown selector kind ' + kind);
}
}
}
function toCallWithExact(method: string, body: string, exact: boolean) {
if (exact)
return `${method}(${quote(body)}, exact=true)`;
return `${method}(${quote(body)})`;
}
function formatValue(value: any): string {
@ -316,12 +362,3 @@ class PythonFormatter {
function quote(text: string) {
return escapeWithQuotes(text, '\"');
}
function asLocator(selector: string, locatorFn = 'locator') {
const match = selector.match(/(.*)\s+>>\s+nth=(\d+)$/);
if (!match)
return `${locatorFn}(${quote(selector)})`;
if (+match[2] === 0)
return `${locatorFn}(${quote(match[1])}).first`;
return `${locatorFn}(${quote(match[1])}).nth(${match[2]})`;
}

View File

@ -25,3 +25,7 @@ export function escapeWithQuotes(text: string, char: string = '\'') {
return char + escapedText.replace(/[`]/g, '`') + char;
throw new Error('Invalid escape char');
}
export function toTitleCase(name: string) {
return name.charAt(0).toUpperCase() + name.substring(1);
}

View File

@ -35,19 +35,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('role=button[name=\"Submit\"]').click();`);
await page.getByRole('button', { name: 'Submit' }).click();`);
expect(sources.get('Python').text).toContain(`
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
page.get_by_role("button", name="Submit").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
await page.get_by_role("button", name="Submit").click()`);
expect(sources.get('Java').text).toContain(`
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
page.getByRole("button", new Page.GetByRoleOptions().setName("Submit")).click()`);
expect(sources.get('C#').text).toContain(`
await page.Locator(\"role=button[name=\\\"Submit\\\"]\").ClickAsync();`);
await page.GetByRole("button", new () { Name = "Submit" }).ClickAsync();`);
expect(message.text()).toBe('click');
});
@ -78,7 +78,7 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('role=button[name=\"Submit\"]').click();`);
await page.getByRole('button', { name: 'Submit' }).click();`);
expect(message.text()).toBe('click');
});
@ -158,19 +158,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('role=button[name=\"Submit\"]').click();`);
await page.getByRole('button', { name: 'Submit' }).click();`);
expect(sources.get('Python').text).toContain(`
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
page.get_by_role("button", name="Submit").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
await page.get_by_role("button", name="Submit").click()`);
expect(sources.get('Java').text).toContain(`
page.locator(\"role=button[name=\\\"Submit\\\"]\").click()`);
page.getByRole("button", new Page.GetByRoleOptions().setName("Submit")).click()`);
expect(sources.get('C#').text).toContain(`
await page.Locator(\"role=button[name=\\\"Submit\\\"]\").ClickAsync();`);
await page.GetByRole("button", new () { Name = "Submit" }).ClickAsync();`);
expect(message.text()).toBe('click');
});
@ -204,7 +204,7 @@ test.describe('cli codegen', () => {
page.dispatchEvent('div', 'click', { detail: 1 })
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Some long text here').click();`);
await page.getByText('Some long text here').click();`);
expect(message.text()).toBe('click');
});
@ -551,28 +551,28 @@ test.describe('cli codegen', () => {
expect(sources.get('JavaScript').text).toContain(`
const [page1] = await Promise.all([
page.waitForEvent('popup'),
page.locator('role=link[name=\"link\"]').click()
page.getByRole('link', { name: 'link' }).click()
]);`);
expect(sources.get('Java').text).toContain(`
Page page1 = page.waitForPopup(() -> {
page.locator("role=link[name=\\\"link\\\"]").click();
page.getByRole("link", new Page.GetByRoleOptions().setName("link")).click();
});`);
expect(sources.get('Python').text).toContain(`
with page.expect_popup() as popup_info:
page.locator(\"role=link[name=\\\"link\\\"]\").click()
page.get_by_role("link", name="link").click()
page1 = popup_info.value`);
expect(sources.get('Python Async').text).toContain(`
async with page.expect_popup() as popup_info:
await page.locator(\"role=link[name=\\\"link\\\"]\").click()
await page.get_by_role("link", name="link").click()
page1 = await popup_info.value`);
expect(sources.get('C#').text).toContain(`
var page1 = await page.RunAndWaitForPopupAsync(async () =>
{
await page.Locator(\"role=link[name=\\\"link\\\"]\").ClickAsync();
await page.GetByRole("link", new () { Name = "link" }).ClickAsync();
});`);
expect(popup.url()).toBe('about:blank');
@ -592,31 +592,31 @@ test.describe('cli codegen', () => {
]);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.locator('text=link').click();
await page.getByText('link').click();
await page.waitForURL('about:blank#foo');`);
expect.soft(sources.get('Playwright Test').text).toContain(`
await page.locator('text=link').click();
await page.getByText('link').click();
await expect(page).toHaveURL('about:blank#foo');`);
expect.soft(sources.get('Java').text).toContain(`
page.locator("text=link").click();
page.getByText("link").click();
assertThat(page).hasURL("about:blank#foo");`);
expect.soft(sources.get('Python').text).toContain(`
page.locator("text=link").click()
page.get_by_text("link").click()
page.wait_for_url("about:blank#foo")`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.locator("text=link").click()
await page.get_by_text("link").click()
await page.wait_for_url("about:blank#foo")`);
expect.soft(sources.get('Pytest').text).toContain(`
page.locator("text=link").click()
page.get_by_text("link").click()
expect(page).to_have_url("about:blank#foo")`);
expect.soft(sources.get('C#').text).toContain(`
await page.Locator("text=link").ClickAsync();
await page.GetByText("link").ClickAsync();
await page.WaitForURLAsync("about:blank#foo");`);
expect(page.url()).toContain('about:blank#foo');
@ -638,23 +638,23 @@ test.describe('cli codegen', () => {
]);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.locator('text=link').click();
await page.getByText('link').click();
await page.waitForURL('about:blank#foo');`);
expect.soft(sources.get('Java').text).toContain(`
page.locator("text=link").click();
page.getByText("link").click();
assertThat(page).hasURL("about:blank#foo");`);
expect.soft(sources.get('Python').text).toContain(`
page.locator(\"text=link\").click()
page.get_by_text("link").click()
page.wait_for_url("about:blank#foo")`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.locator(\"text=link\").click()
await page.get_by_text("link").click()
await page.wait_for_url("about:blank#foo")`);
expect.soft(sources.get('C#').text).toContain(`
await page.Locator(\"text=link\").ClickAsync();
await page.GetByText("link").ClickAsync();
await page.WaitForURLAsync(\"about:blank#foo\");`);
expect(page.url()).toContain('about:blank#foo');
@ -685,22 +685,22 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('text=Click me').click({
await page.getByText('Click me').click({
button: 'middle'
});`);
expect(sources.get('Python').text).toContain(`
page.locator("text=Click me").click(button="middle")`);
page.get_by_text("Click me").click(button="middle")`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("text=Click me").click(button="middle")`);
await page.get_by_text("Click me").click(button="middle")`);
expect(sources.get('Java').text).toContain(`
page.locator("text=Click me").click(new Locator.ClickOptions()
page.getByText("Click me").click(new Locator.ClickOptions()
.setButton(MouseButton.MIDDLE));`);
expect(sources.get('C#').text).toContain(`
await page.Locator("text=Click me").ClickAsync(new LocatorClickOptions
await page.GetByText("Click me").ClickAsync(new LocatorClickOptions
{
Button = MouseButton.Middle,
});`);

View File

@ -231,28 +231,28 @@ test.describe('cli codegen', () => {
expect(sources.get('JavaScript').text).toContain(`
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('role=link[name=\"Download\"]').click()
page.getByRole('link', { name: 'Download' }).click()
]);`);
expect(sources.get('Java').text).toContain(`
BrowserContext context = browser.newContext();`);
expect(sources.get('Java').text).toContain(`
Download download = page.waitForDownload(() -> {
page.locator("role=link[name=\\\"Download\\\"]").click();
page.getByRole("link", new Page.GetByRoleOptions().setName("Download")).click();
});`);
expect(sources.get('Python').text).toContain(`
context = browser.new_context()`);
expect(sources.get('Python').text).toContain(`
with page.expect_download() as download_info:
page.locator(\"role=link[name=\\\"Download\\\"]\").click()
page.get_by_role("link", name="Download").click()
download = download_info.value`);
expect(sources.get('Python Async').text).toContain(`
context = await browser.new_context()`);
expect(sources.get('Python Async').text).toContain(`
async with page.expect_download() as download_info:
await page.locator(\"role=link[name=\\\"Download\\\"]\").click()
await page.get_by_role("link", name="Download").click()
download = await download_info.value`);
expect(sources.get('C#').text).toContain(`
@ -260,7 +260,7 @@ test.describe('cli codegen', () => {
expect(sources.get('C#').text).toContain(`
var download1 = await page.RunAndWaitForDownloadAsync(async () =>
{
await page.Locator(\"role=link[name=\\\"Download\\\"]\").ClickAsync();
await page.GetByRole("link", new () { Name = "Download" }).ClickAsync();
});`);
});
@ -283,22 +283,22 @@ test.describe('cli codegen', () => {
console.log(\`Dialog message: \${dialog.message()}\`);
dialog.dismiss().catch(() => {});
});
await page.locator('role=button[name=\"click me\"]').click();`);
await page.getByRole('button', { name: 'click me' }).click();`);
expect(sources.get('Java').text).toContain(`
page.onceDialog(dialog -> {
System.out.println(String.format("Dialog message: %s", dialog.message()));
dialog.dismiss();
});
page.locator("role=button[name=\\\"click me\\\"]").click();`);
page.getByRole("button", new Page.GetByRoleOptions().setName("click me")).click();`);
expect(sources.get('Python').text).toContain(`
page.once(\"dialog\", lambda dialog: dialog.dismiss())
page.locator(\"role=button[name=\\\"click me\\\"]\").click()`);
page.get_by_role("button", name="click me").click()`);
expect(sources.get('Python Async').text).toContain(`
page.once(\"dialog\", lambda dialog: dialog.dismiss())
await page.locator(\"role=button[name=\\\"click me\\\"]\").click()`);
await page.get_by_role("button", name="click me").click()`);
expect(sources.get('C#').text).toContain(`
void page_Dialog1_EventHandler(object sender, IDialog dialog)
@ -308,7 +308,7 @@ test.describe('cli codegen', () => {
page.Dialog -= page_Dialog1_EventHandler;
}
page.Dialog += page_Dialog1_EventHandler;
await page.Locator(\"role=button[name=\\\"click me\\\"]\").ClickAsync();`);
await page.GetByRole("button", new () { Name = "click me" }).ClickAsync();`);
});
@ -352,7 +352,7 @@ test.describe('cli codegen', () => {
expect(sources.get('JavaScript').text).toContain(`
const [page1] = await Promise.all([
page.waitForEvent('popup'),
page.locator('role=link[name=\"link\"]').click({
page.getByRole('link', { name: 'link' }).click({
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
})
]);`);

View File

@ -36,20 +36,20 @@ test.describe('cli codegen', () => {
page.dispatchEvent('button', 'click', { detail: 1 })
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('role=button[name=\"Submit\"]').first().click();`);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.getByRole('button', { name: 'Submit' }).first().click();`);
expect(sources.get('Python').text).toContain(`
page.locator("role=button[name=\\\"Submit\\\"]").first.click()`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_role("button", name="Submit").first.click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("role=button[name=\\\"Submit\\\"]").first.click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_role("button", name="Submit").first.click()`);
expect(sources.get('Java').text).toContain(`
page.locator("role=button[name=\\\"Submit\\\"]").first().click();`);
expect.soft(sources.get('Java').text).toContain(`
page.getByRole("button", new Page.GetByRoleOptions().setName("Submit")).first().click();`);
expect(sources.get('C#').text).toContain(`
await page.Locator("role=button[name=\\\"Submit\\\"]").First.ClickAsync();`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByRole("button", new () { Name = "Submit" }).First.ClickAsync();`);
expect(message.text()).toBe('click1');
});
@ -71,20 +71,20 @@ test.describe('cli codegen', () => {
page.dispatchEvent('button', 'click', { detail: 1 })
]);
expect(sources.get('JavaScript').text).toContain(`
await page.locator('role=button[name=\"Submit\"]').nth(1).click();`);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.getByRole('button', { name: 'Submit' }).nth(1).click();`);
expect(sources.get('Python').text).toContain(`
page.locator("role=button[name=\\\"Submit\\\"]").nth(1).click()`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_role("button", name="Submit").nth(1).click()`);
expect(sources.get('Python Async').text).toContain(`
await page.locator("role=button[name=\\\"Submit\\\"]").nth(1).click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_role("button", name="Submit").nth(1).click()`);
expect(sources.get('Java').text).toContain(`
page.locator("role=button[name=\\\"Submit\\\"]").nth(1).click();`);
expect.soft(sources.get('Java').text).toContain(`
page.getByRole("button", new Page.GetByRoleOptions().setName("Submit")).nth(1).click();`);
expect(sources.get('C#').text).toContain(`
await page.Locator("role=button[name=\\\"Submit\\\"]").Nth(1).ClickAsync();`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByRole("button", new () { Name = "Submit" }).Nth(1).ClickAsync();`);
expect(message.text()).toBe('click2');
});
@ -121,19 +121,19 @@ test.describe('cli codegen', () => {
]);
expect(sources.get('JavaScript').text).toContain(`
await page.frameLocator('#frame1').locator('text=Hello1').click();`);
await page.frameLocator('#frame1').getByText('Hello1').click();`);
expect(sources.get('Java').text).toContain(`
page.frameLocator("#frame1").locator("text=Hello1").click();`);
page.frameLocator("#frame1").getByText("Hello1").click();`);
expect(sources.get('Python').text).toContain(`
page.frame_locator("#frame1").locator("text=Hello1").click()`);
page.frame_locator("#frame1").get_by_text("Hello1").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.frame_locator("#frame1").locator("text=Hello1").click()`);
await page.frame_locator("#frame1").get_by_text("Hello1").click()`);
expect(sources.get('C#').text).toContain(`
await page.FrameLocator("#frame1").Locator("text=Hello1").ClickAsync();`);
await page.FrameLocator("#frame1").GetByText("Hello1").ClickAsync();`);
[sources] = await Promise.all([
@ -141,20 +141,20 @@ test.describe('cli codegen', () => {
frameHello2.click('text=Hello2'),
]);
expect(sources.get('JavaScript').text).toContain(`
await page.frameLocator('#frame1').frameLocator('iframe').locator('text=Hello2').click();`);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.frameLocator('#frame1').frameLocator('iframe').getByText('Hello2').click();`);
expect(sources.get('Java').text).toContain(`
page.frameLocator("#frame1").frameLocator("iframe").locator("text=Hello2").click();`);
expect.soft(sources.get('Java').text).toContain(`
page.frameLocator("#frame1").frameLocator("iframe").getByText("Hello2").click();`);
expect(sources.get('Python').text).toContain(`
page.frame_locator("#frame1").frame_locator("iframe").locator("text=Hello2").click()`);
expect.soft(sources.get('Python').text).toContain(`
page.frame_locator("#frame1").frame_locator("iframe").get_by_text("Hello2").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.frame_locator("#frame1").frame_locator("iframe").locator("text=Hello2").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.frame_locator("#frame1").frame_locator("iframe").get_by_text("Hello2").click()`);
expect(sources.get('C#').text).toContain(`
await page.FrameLocator("#frame1").FrameLocator("iframe").Locator("text=Hello2").ClickAsync();`);
expect.soft(sources.get('C#').text).toContain(`
await page.FrameLocator("#frame1").FrameLocator("iframe").GetByText("Hello2").ClickAsync();`);
[sources] = await Promise.all([
@ -162,22 +162,22 @@ test.describe('cli codegen', () => {
frameOne.click('text=HelloNameOne'),
]);
expect(sources.get('JavaScript').text).toContain(`
expect.soft(sources.get('JavaScript').text).toContain(`
await page.frame({
name: 'one'
}).locator('text=HelloNameOne').click();`);
}).getByText('HelloNameOne').click();`);
expect(sources.get('Java').text).toContain(`
page.frame("one").locator("text=HelloNameOne").click();`);
expect.soft(sources.get('Java').text).toContain(`
page.frame("one").getByText("HelloNameOne").click();`);
expect(sources.get('Python').text).toContain(`
page.frame(name=\"one\").locator(\"text=HelloNameOne\").click()`);
expect.soft(sources.get('Python').text).toContain(`
page.frame(name=\"one\").get_by_text(\"HelloNameOne\").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.frame(name=\"one\").locator(\"text=HelloNameOne\").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.frame(name=\"one\").get_by_text(\"HelloNameOne\").click()`);
expect(sources.get('C#').text).toContain(`
await page.Frame(\"one\").Locator(\"text=HelloNameOne\").ClickAsync();`);
expect.soft(sources.get('C#').text).toContain(`
await page.Frame(\"one\").GetByText(\"HelloNameOne\").ClickAsync();`);
[sources] = await Promise.all([
@ -185,21 +185,138 @@ test.describe('cli codegen', () => {
frameAnonymous.click('text=HelloNameAnonymous'),
]);
expect(sources.get('JavaScript').text).toContain(`
expect.soft(sources.get('JavaScript').text).toContain(`
await page.frame({
url: 'about:blank'
}).locator('text=HelloNameAnonymous').click();`);
}).getByText('HelloNameAnonymous').click();`);
expect(sources.get('Java').text).toContain(`
page.frameByUrl("about:blank").locator("text=HelloNameAnonymous").click();`);
expect.soft(sources.get('Java').text).toContain(`
page.frameByUrl("about:blank").getByText("HelloNameAnonymous").click();`);
expect(sources.get('Python').text).toContain(`
page.frame(url=\"about:blank\").locator(\"text=HelloNameAnonymous\").click()`);
expect.soft(sources.get('Python').text).toContain(`
page.frame(url=\"about:blank\").get_by_text(\"HelloNameAnonymous\").click()`);
expect(sources.get('Python Async').text).toContain(`
await page.frame(url=\"about:blank\").locator(\"text=HelloNameAnonymous\").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.frame(url=\"about:blank\").get_by_text(\"HelloNameAnonymous\").click()`);
expect(sources.get('C#').text).toContain(`
await page.FrameByUrl(\"about:blank\").Locator(\"text=HelloNameAnonymous\").ClickAsync();`);
expect.soft(sources.get('C#').text).toContain(`
await page.FrameByUrl(\"about:blank\").GetByText(\"HelloNameAnonymous\").ClickAsync();`);
});
test('should generate role locators undef frame locators', async ({ page, openRecorder, server }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<iframe id=frame1 srcdoc="<button>Submit</button>">`, server.EMPTY_PAGE, 2);
const frame = page.mainFrame().childFrames()[0];
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
frame.click('button'),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.frameLocator('#frame1').getByRole('button', { name: 'Submit' }).click();`);
expect.soft(sources.get('Java').text).toContain(`
page.frameLocator("#frame1").getByRole("button", new FrameLocator.GetByRoleOptions().setName("Submit")).click();`);
expect.soft(sources.get('Python').text).toContain(`
page.frame_locator("#frame1").get_by_role("button", name="Submit").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.frame_locator("#frame1").get_by_role("button", name="Submit").click()`);
expect.soft(sources.get('C#').text).toContain(`
await page.FrameLocator("#frame1").GetByRole("button", new () { Name = "Submit" }).ClickAsync();`);
});
test('should generate getByTestId', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<div data-testid=testid onclick="console.log('click')">Submit</div>`);
const selector = await recorder.hoverOverElement('div');
expect(selector).toBe('attr=[data-testid="testid"]');
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
page.dispatchEvent('div', 'click', { detail: 1 })
]);
expect.soft(sources.get('JavaScript').text).toContain(`
await page.getByTestId('testid').click();`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_test_id("testid").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_test_id("testid").click()`);
expect.soft(sources.get('Java').text).toContain(`
page.getByTestId("testid").click()`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByTestId("testid").ClickAsync();`);
expect(message.text()).toBe('click');
});
test('should generate getByPlaceholderText', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input placeholder="Country"></input>`);
const selector = await recorder.hoverOverElement('input');
expect(selector).toBe('attr=[placeholder="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.getByPlaceholderText('Country').click();`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_placeholder_text("Country").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_placeholder_text("Country").click()`);
expect.soft(sources.get('Java').text).toContain(`
page.getByPlaceholderText("Country").click()`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByPlaceholderText("Country").ClickAsync();`);
});
test('should generate getByAltText', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input alt="Country"></input>`);
const selector = await recorder.hoverOverElement('input');
expect(selector).toBe('attr=[alt="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.getByAltText('Country').click();`);
expect.soft(sources.get('Python').text).toContain(`
page.get_by_alt_text("Country").click()`);
expect.soft(sources.get('Python Async').text).toContain(`
await page.get_by_alt_text("Country").click()`);
expect.soft(sources.get('Java').text).toContain(`
page.getByAltText("Country").click()`);
expect.soft(sources.get('C#').text).toContain(`
await page.GetByAltText("Country").ClickAsync();`);
});
});