mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-13 17:14:02 +03:00
chore: generate api calls (#17794)
This commit is contained in:
parent
ade75d66cf
commit
1311767f87
@ -1,5 +1,6 @@
|
||||
[*]
|
||||
../
|
||||
../isomorphic/**
|
||||
../registry/**
|
||||
../../common/
|
||||
../../protocol/
|
||||
|
@ -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]})`;
|
||||
}
|
||||
|
@ -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]})`;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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('.');
|
||||
}
|
||||
|
@ -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]})`;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
});`);
|
||||
|
@ -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'}']
|
||||
})
|
||||
]);`);
|
||||
|
@ -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();`);
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user