mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(codegen): generate locators and frame locators (#11873)
This commit is contained in:
parent
46dfa45b4e
commit
f82e09be04
@ -44,7 +44,7 @@ export class Highlight {
|
|||||||
this._innerGlassPaneElement.appendChild(this._tooltipElement);
|
this._innerGlassPaneElement.appendChild(this._tooltipElement);
|
||||||
|
|
||||||
// Use a closed shadow root to prevent selectors matching our internal previews.
|
// Use a closed shadow root to prevent selectors matching our internal previews.
|
||||||
this._glassPaneShadow = this._outerGlassPaneElement.attachShadow({ mode: this._isUnderTest ? 'open' : 'closed' });
|
this._glassPaneShadow = this._outerGlassPaneElement.attachShadow({ mode: 'closed' });
|
||||||
this._glassPaneShadow.appendChild(this._innerGlassPaneElement);
|
this._glassPaneShadow.appendChild(this._innerGlassPaneElement);
|
||||||
this._glassPaneShadow.appendChild(this._actionPointElement);
|
this._glassPaneShadow.appendChild(this._actionPointElement);
|
||||||
const styleElement = document.createElement('style');
|
const styleElement = document.createElement('style');
|
||||||
@ -105,6 +105,8 @@ export class Highlight {
|
|||||||
this._actionPointElement.style.top = y + 'px';
|
this._actionPointElement.style.top = y + 'px';
|
||||||
this._actionPointElement.style.left = x + 'px';
|
this._actionPointElement.style.left = x + 'px';
|
||||||
this._actionPointElement.hidden = false;
|
this._actionPointElement.hidden = false;
|
||||||
|
if (this._isUnderTest)
|
||||||
|
console.error('Action point for test: ' + JSON.stringify({ x, y })); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
hideActionPoint() {
|
hideActionPoint() {
|
||||||
@ -162,6 +164,9 @@ export class Highlight {
|
|||||||
highlightElement.style.height = box.height + 'px';
|
highlightElement.style.height = box.height + 'px';
|
||||||
highlightElement.style.display = 'block';
|
highlightElement.style.display = 'block';
|
||||||
this._highlightElements.push(highlightElement);
|
this._highlightElements.push(highlightElement);
|
||||||
|
|
||||||
|
if (this._isUnderTest)
|
||||||
|
console.error('Highlight box for test: ' + JSON.stringify({ x: box.x, y: box.y, width: box.width, height: box.height })); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const highlightElement of pool) {
|
for (const highlightElement of pool) {
|
||||||
|
|||||||
@ -124,6 +124,10 @@ export class InjectedScript {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateSelector(targetElement: Element): string {
|
||||||
|
return generateSelector(this, targetElement, true).selector;
|
||||||
|
}
|
||||||
|
|
||||||
querySelector(selector: ParsedSelector, root: Node, strict: boolean): Element | undefined {
|
querySelector(selector: ParsedSelector, root: Node, strict: boolean): Element | undefined {
|
||||||
if (!(root as any)['querySelector'])
|
if (!(root as any)['querySelector'])
|
||||||
throw this.createStacklessError('Node is not queryable.');
|
throw this.createStacklessError('Node is not queryable.');
|
||||||
@ -848,7 +852,7 @@ export class InjectedScript {
|
|||||||
strictModeViolationError(selector: ParsedSelector, matches: Element[]): Error {
|
strictModeViolationError(selector: ParsedSelector, matches: Element[]): Error {
|
||||||
const infos = matches.slice(0, 10).map(m => ({
|
const infos = matches.slice(0, 10).map(m => ({
|
||||||
preview: this.previewNode(m),
|
preview: this.previewNode(m),
|
||||||
selector: generateSelector(this, m, true).selector
|
selector: this.generateSelector(m),
|
||||||
}));
|
}));
|
||||||
const lines = infos.map((info, i) => `\n ${i + 1}) ${info.preview} aka playwright.$("${info.selector}")`);
|
const lines = infos.map((info, i) => `\n ${i + 1}) ${info.preview} aka playwright.$("${info.selector}")`);
|
||||||
if (infos.length < matches.length)
|
if (infos.length < matches.length)
|
||||||
|
|||||||
@ -18,14 +18,10 @@ import { EventEmitter } from 'events';
|
|||||||
import type { BrowserContextOptions, LaunchOptions } from '../../../..';
|
import type { BrowserContextOptions, LaunchOptions } from '../../../..';
|
||||||
import { Frame } from '../../frames';
|
import { Frame } from '../../frames';
|
||||||
import { LanguageGenerator, LanguageGeneratorOptions } from './language';
|
import { LanguageGenerator, LanguageGeneratorOptions } from './language';
|
||||||
import { Action, Signal } from './recorderActions';
|
import { Action, Signal, FrameDescription } from './recorderActions';
|
||||||
import { describeFrame } from './utils';
|
|
||||||
|
|
||||||
export type ActionInContext = {
|
export type ActionInContext = {
|
||||||
pageAlias: string;
|
frame: FrameDescription;
|
||||||
frameName?: string;
|
|
||||||
frameUrl: string;
|
|
||||||
isMainFrame: boolean;
|
|
||||||
action: Action;
|
action: Action;
|
||||||
committed?: boolean;
|
committed?: boolean;
|
||||||
};
|
};
|
||||||
@ -82,10 +78,10 @@ export class CodeGenerator extends EventEmitter {
|
|||||||
didPerformAction(actionInContext: ActionInContext) {
|
didPerformAction(actionInContext: ActionInContext) {
|
||||||
if (!this._enabled)
|
if (!this._enabled)
|
||||||
return;
|
return;
|
||||||
const { action, pageAlias } = actionInContext;
|
const action = actionInContext.action;
|
||||||
let eraseLastAction = false;
|
let eraseLastAction = false;
|
||||||
if (this._lastAction && this._lastAction.pageAlias === pageAlias) {
|
if (this._lastAction && this._lastAction.frame.pageAlias === actionInContext.frame.pageAlias) {
|
||||||
const { action: lastAction } = this._lastAction;
|
const lastAction = this._lastAction.action;
|
||||||
// We augment last action based on the type.
|
// We augment last action based on the type.
|
||||||
if (this._lastAction && action.name === 'fill' && lastAction.name === 'fill') {
|
if (this._lastAction && action.name === 'fill' && lastAction.name === 'fill') {
|
||||||
if (action.selector === lastAction.selector)
|
if (action.selector === lastAction.selector)
|
||||||
@ -148,8 +144,11 @@ export class CodeGenerator extends EventEmitter {
|
|||||||
|
|
||||||
if (signal.name === 'navigation') {
|
if (signal.name === 'navigation') {
|
||||||
this.addAction({
|
this.addAction({
|
||||||
pageAlias,
|
frame: {
|
||||||
...describeFrame(frame),
|
pageAlias,
|
||||||
|
isMainFrame: frame._page.mainFrame() === frame,
|
||||||
|
url: frame.url(),
|
||||||
|
},
|
||||||
committed: true,
|
committed: true,
|
||||||
action: {
|
action: {
|
||||||
name: 'navigate',
|
name: 'navigate',
|
||||||
|
|||||||
@ -28,7 +28,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
|||||||
highlighter = 'csharp';
|
highlighter = 'csharp';
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext): string {
|
generateAction(actionInContext: ActionInContext): string {
|
||||||
const { action, pageAlias } = actionInContext;
|
const action = actionInContext.action;
|
||||||
|
const pageAlias = actionInContext.frame.pageAlias;
|
||||||
const formatter = new CSharpFormatter(8);
|
const formatter = new CSharpFormatter(8);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('// ' + actionTitle(action));
|
formatter.add('// ' + actionTitle(action));
|
||||||
@ -40,10 +41,17 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
|||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = actionInContext.isMainFrame ? pageAlias :
|
let subject: string;
|
||||||
(actionInContext.frameName ?
|
if (actionInContext.frame.isMainFrame) {
|
||||||
`${pageAlias}.Frame(${quote(actionInContext.frameName)})` :
|
subject = pageAlias;
|
||||||
`${pageAlias}.FrameByUrl(${quote(actionInContext.frameUrl)})`);
|
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
|
||||||
|
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'FrameLocator'));
|
||||||
|
subject = `${pageAlias}${locators.join('')}`;
|
||||||
|
} else if (actionInContext.frame.name) {
|
||||||
|
subject = `${pageAlias}.Frame(${quote(actionInContext.frame.name)})`;
|
||||||
|
} else {
|
||||||
|
subject = `${pageAlias}.FrameByUrl(${quote(actionInContext.frame.url)})`;
|
||||||
|
}
|
||||||
|
|
||||||
const signals = toSignalMap(action);
|
const signals = toSignalMap(action);
|
||||||
|
|
||||||
@ -58,12 +66,12 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
const actionCall = this._generateActionCall(action, actionInContext.isMainFrame);
|
const actionCall = this._generateActionCall(action, actionInContext.frame.isMainFrame);
|
||||||
if (signals.waitForNavigation) {
|
if (signals.waitForNavigation) {
|
||||||
lines.push(`await ${pageAlias}.RunAndWaitForNavigationAsync(async () =>`);
|
lines.push(`await ${pageAlias}.RunAndWaitForNavigationAsync(async () =>`);
|
||||||
lines.push(`{`);
|
lines.push(`{`);
|
||||||
lines.push(` await ${subject}.${actionCall};`);
|
lines.push(` await ${subject}.${actionCall};`);
|
||||||
lines.push(`}/*, new ${actionInContext.isMainFrame ? 'Page' : 'Frame'}WaitForNavigationOptions`);
|
lines.push(`}/*, new ${actionInContext.frame.isMainFrame ? 'Page' : 'Frame'}WaitForNavigationOptions`);
|
||||||
lines.push(`{`);
|
lines.push(`{`);
|
||||||
lines.push(` UrlString = ${quote(signals.waitForNavigation.url)}`);
|
lines.push(` UrlString = ${quote(signals.waitForNavigation.url)}`);
|
||||||
lines.push(`}*/);`);
|
lines.push(`}*/);`);
|
||||||
@ -110,27 +118,27 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
|||||||
if (action.position)
|
if (action.position)
|
||||||
options.position = action.position;
|
options.position = action.position;
|
||||||
if (!Object.entries(options).length)
|
if (!Object.entries(options).length)
|
||||||
return `${method}Async(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.${method}Async()`;
|
||||||
const optionsString = formatObject(options, ' ', (isPage ? 'Page' : 'Frame') + method + 'Options');
|
const optionsString = formatObject(options, ' ', 'Locator' + method + 'Options');
|
||||||
return `${method}Async(${quote(action.selector)}, ${optionsString})`;
|
return asLocator(action.selector) + `.${method}Async(${optionsString})`;
|
||||||
}
|
}
|
||||||
case 'check':
|
case 'check':
|
||||||
return `CheckAsync(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.CheckAsync()`;
|
||||||
case 'uncheck':
|
case 'uncheck':
|
||||||
return `UncheckAsync(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.UncheckAsync()`;
|
||||||
case 'fill':
|
case 'fill':
|
||||||
return `FillAsync(${quote(action.selector)}, ${quote(action.text)})`;
|
return asLocator(action.selector) + `.FillAsync(${quote(action.text)})`;
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `SetInputFilesAsync(${quote(action.selector)}, ${formatObject(action.files)})`;
|
return asLocator(action.selector) + `.SetInputFilesAsync(${formatObject(action.files)})`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `PressAsync(${quote(action.selector)}, ${quote(shortcut)})`;
|
return asLocator(action.selector) + `.PressAsync(${quote(shortcut)})`;
|
||||||
}
|
}
|
||||||
case 'navigate':
|
case 'navigate':
|
||||||
return `GotoAsync(${quote(action.url)})`;
|
return `GotoAsync(${quote(action.url)})`;
|
||||||
case 'select':
|
case 'select':
|
||||||
return `SelectOptionAsync(${quote(action.selector)}, ${formatObject(action.options)})`;
|
return asLocator(action.selector) + `.SelectOptionAsync(${formatObject(action.options)})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,4 +278,13 @@ class CSharpFormatter {
|
|||||||
|
|
||||||
function quote(text: string) {
|
function quote(text: string) {
|
||||||
return escapeWithQuotes(text, '\"');
|
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]})`;
|
||||||
|
}
|
||||||
|
|||||||
@ -29,7 +29,8 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
|||||||
highlighter = 'java';
|
highlighter = 'java';
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext): string {
|
generateAction(actionInContext: ActionInContext): string {
|
||||||
const { action, pageAlias } = actionInContext;
|
const action = actionInContext.action;
|
||||||
|
const pageAlias = actionInContext.frame.pageAlias;
|
||||||
const formatter = new JavaScriptFormatter(6);
|
const formatter = new JavaScriptFormatter(6);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('// ' + actionTitle(action));
|
formatter.add('// ' + actionTitle(action));
|
||||||
@ -41,10 +42,17 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
|||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = actionInContext.isMainFrame ? pageAlias :
|
let subject: string;
|
||||||
(actionInContext.frameName ?
|
if (actionInContext.frame.isMainFrame) {
|
||||||
`${pageAlias}.frame(${quote(actionInContext.frameName)})` :
|
subject = pageAlias;
|
||||||
`${pageAlias}.frameByUrl(${quote(actionInContext.frameUrl)})`);
|
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
|
||||||
|
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'frameLocator'));
|
||||||
|
subject = `${pageAlias}${locators.join('')}`;
|
||||||
|
} else if (actionInContext.frame.name) {
|
||||||
|
subject = `${pageAlias}.frame(${quote(actionInContext.frame.name)})`;
|
||||||
|
} else {
|
||||||
|
subject = `${pageAlias}.frameByUrl(${quote(actionInContext.frame.url)})`;
|
||||||
|
}
|
||||||
|
|
||||||
const signals = toSignalMap(action);
|
const signals = toSignalMap(action);
|
||||||
|
|
||||||
@ -55,7 +63,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
|||||||
});`);
|
});`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionCall = this._generateActionCall(action, actionInContext.isMainFrame);
|
const actionCall = this._generateActionCall(action);
|
||||||
let code = `${subject}.${actionCall};`;
|
let code = `${subject}.${actionCall};`;
|
||||||
|
|
||||||
if (signals.popup) {
|
if (signals.popup) {
|
||||||
@ -85,7 +93,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
|||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateActionCall(action: Action, isPage: boolean): string {
|
private _generateActionCall(action: Action): string {
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'openPage':
|
case 'openPage':
|
||||||
throw Error('Not reached');
|
throw Error('Not reached');
|
||||||
@ -105,26 +113,26 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
|||||||
options.clickCount = action.clickCount;
|
options.clickCount = action.clickCount;
|
||||||
if (action.position)
|
if (action.position)
|
||||||
options.position = action.position;
|
options.position = action.position;
|
||||||
const optionsText = formatClickOptions(options, isPage);
|
const optionsText = formatClickOptions(options);
|
||||||
return `${method}(${quote(action.selector)}${optionsText ? ', ' : ''}${optionsText})`;
|
return asLocator(action.selector) + `.${method}(${optionsText})`;
|
||||||
}
|
}
|
||||||
case 'check':
|
case 'check':
|
||||||
return `check(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.check()`;
|
||||||
case 'uncheck':
|
case 'uncheck':
|
||||||
return `uncheck(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.uncheck()`;
|
||||||
case 'fill':
|
case 'fill':
|
||||||
return `fill(${quote(action.selector)}, ${quote(action.text)})`;
|
return asLocator(action.selector) + `.fill(${quote(action.text)})`;
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `setInputFiles(${quote(action.selector)}, ${formatPath(action.files.length === 1 ? action.files[0] : action.files)})`;
|
return asLocator(action.selector) + `.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)})`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `press(${quote(action.selector)}, ${quote(shortcut)})`;
|
return asLocator(action.selector) + `.press(${quote(shortcut)})`;
|
||||||
}
|
}
|
||||||
case 'navigate':
|
case 'navigate':
|
||||||
return `navigate(${quote(action.url)})`;
|
return `navigate(${quote(action.url)})`;
|
||||||
case 'select':
|
case 'select':
|
||||||
return `selectOption(${quote(action.selector)}, ${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])})`;
|
return asLocator(action.selector) + `.selectOption(${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +225,7 @@ function formatContextOptions(contextOptions: BrowserContextOptions, deviceName:
|
|||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatClickOptions(options: MouseClickOptions, isPage: boolean) {
|
function formatClickOptions(options: MouseClickOptions) {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
if (options.button)
|
if (options.button)
|
||||||
lines.push(` .setButton(MouseButton.${options.button.toUpperCase()})`);
|
lines.push(` .setButton(MouseButton.${options.button.toUpperCase()})`);
|
||||||
@ -229,10 +237,19 @@ function formatClickOptions(options: MouseClickOptions, isPage: boolean) {
|
|||||||
lines.push(` .setPosition(${options.position.x}, ${options.position.y})`);
|
lines.push(` .setPosition(${options.position.x}, ${options.position.y})`);
|
||||||
if (!lines.length)
|
if (!lines.length)
|
||||||
return '';
|
return '';
|
||||||
lines.unshift(`new ${isPage ? 'Page' : 'Frame'}.ClickOptions()`);
|
lines.unshift(`new Locator.ClickOptions()`);
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function quote(text: string) {
|
function quote(text: string) {
|
||||||
return escapeWithQuotes(text, '\"');
|
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]})`;
|
||||||
|
}
|
||||||
|
|||||||
@ -35,7 +35,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext): string {
|
generateAction(actionInContext: ActionInContext): string {
|
||||||
const { action, pageAlias } = actionInContext;
|
const action = actionInContext.action;
|
||||||
|
const pageAlias = actionInContext.frame.pageAlias;
|
||||||
const formatter = new JavaScriptFormatter(2);
|
const formatter = new JavaScriptFormatter(2);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('// ' + actionTitle(action));
|
formatter.add('// ' + actionTitle(action));
|
||||||
@ -49,10 +50,17 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
|||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = actionInContext.isMainFrame ? pageAlias :
|
let subject: string;
|
||||||
(actionInContext.frameName ?
|
if (actionInContext.frame.isMainFrame) {
|
||||||
`${pageAlias}.frame(${formatObject({ name: actionInContext.frameName })})` :
|
subject = pageAlias;
|
||||||
`${pageAlias}.frame(${formatObject({ url: actionInContext.frameUrl })})`);
|
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
|
||||||
|
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'frameLocator'));
|
||||||
|
subject = `${pageAlias}${locators.join('')}`;
|
||||||
|
} else if (actionInContext.frame.name) {
|
||||||
|
subject = `${pageAlias}.frame(${formatObject({ name: actionInContext.frame.name })})`;
|
||||||
|
} else {
|
||||||
|
subject = `${pageAlias}.frame(${formatObject({ url: actionInContext.frame.url })})`;
|
||||||
|
}
|
||||||
|
|
||||||
const signals = toSignalMap(action);
|
const signals = toSignalMap(action);
|
||||||
|
|
||||||
@ -123,26 +131,26 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
|||||||
options.clickCount = action.clickCount;
|
options.clickCount = action.clickCount;
|
||||||
if (action.position)
|
if (action.position)
|
||||||
options.position = action.position;
|
options.position = action.position;
|
||||||
const optionsString = formatOptions(options);
|
const optionsString = formatOptions(options, false);
|
||||||
return `${method}(${quote(action.selector)}${optionsString})`;
|
return asLocator(action.selector) + `.${method}(${optionsString})`;
|
||||||
}
|
}
|
||||||
case 'check':
|
case 'check':
|
||||||
return `check(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.check()`;
|
||||||
case 'uncheck':
|
case 'uncheck':
|
||||||
return `uncheck(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.uncheck()`;
|
||||||
case 'fill':
|
case 'fill':
|
||||||
return `fill(${quote(action.selector)}, ${quote(action.text)})`;
|
return asLocator(action.selector) + `.fill(${quote(action.text)})`;
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `setInputFiles(${quote(action.selector)}, ${formatObject(action.files.length === 1 ? action.files[0] : action.files)})`;
|
return asLocator(action.selector) + `.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)})`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `press(${quote(action.selector)}, ${quote(shortcut)})`;
|
return asLocator(action.selector) + `.press(${quote(shortcut)})`;
|
||||||
}
|
}
|
||||||
case 'navigate':
|
case 'navigate':
|
||||||
return `goto(${quote(action.url)})`;
|
return `goto(${quote(action.url)})`;
|
||||||
case 'select':
|
case 'select':
|
||||||
return `selectOption(${quote(action.selector)}, ${formatObject(action.options.length > 1 ? action.options : action.options[0])})`;
|
return asLocator(action.selector) + `.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,11 +200,20 @@ ${useText ? '\ntest.use(' + useText + ');\n' : ''}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatOptions(value: any): string {
|
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 formatOptions(value: any, hasArguments: boolean): string {
|
||||||
const keys = Object.keys(value);
|
const keys = Object.keys(value);
|
||||||
if (!keys.length)
|
if (!keys.length)
|
||||||
return '';
|
return '';
|
||||||
return ', ' + formatObject(value);
|
return (hasArguments ? ', ' : '') + formatObject(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatObject(value: any, indent = ' '): string {
|
function formatObject(value: any, indent = ' '): string {
|
||||||
|
|||||||
@ -40,7 +40,8 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateAction(actionInContext: ActionInContext): string {
|
generateAction(actionInContext: ActionInContext): string {
|
||||||
const { action, pageAlias } = actionInContext;
|
const action = actionInContext.action;
|
||||||
|
const pageAlias = actionInContext.frame.pageAlias;
|
||||||
const formatter = new PythonFormatter(4);
|
const formatter = new PythonFormatter(4);
|
||||||
formatter.newLine();
|
formatter.newLine();
|
||||||
formatter.add('# ' + actionTitle(action));
|
formatter.add('# ' + actionTitle(action));
|
||||||
@ -52,10 +53,17 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
|||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = actionInContext.isMainFrame ? pageAlias :
|
let subject: string;
|
||||||
(actionInContext.frameName ?
|
if (actionInContext.frame.isMainFrame) {
|
||||||
`${pageAlias}.frame(${formatOptions({ name: actionInContext.frameName }, false)})` :
|
subject = pageAlias;
|
||||||
`${pageAlias}.frame(${formatOptions({ url: actionInContext.frameUrl }, false)})`);
|
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
|
||||||
|
const locators = actionInContext.frame.selectorsChain.map(selector => '.' + asLocator(selector, 'frame_locator'));
|
||||||
|
subject = `${pageAlias}${locators.join('')}`;
|
||||||
|
} else if (actionInContext.frame.name) {
|
||||||
|
subject = `${pageAlias}.frame(${formatOptions({ name: actionInContext.frame.name }, false)})`;
|
||||||
|
} else {
|
||||||
|
subject = `${pageAlias}.frame(${formatOptions({ url: actionInContext.frame.url }, false)})`;
|
||||||
|
}
|
||||||
|
|
||||||
const signals = toSignalMap(action);
|
const signals = toSignalMap(action);
|
||||||
|
|
||||||
@ -114,26 +122,26 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
|||||||
options.clickCount = action.clickCount;
|
options.clickCount = action.clickCount;
|
||||||
if (action.position)
|
if (action.position)
|
||||||
options.position = action.position;
|
options.position = action.position;
|
||||||
const optionsString = formatOptions(options, true);
|
const optionsString = formatOptions(options, false);
|
||||||
return `${method}(${quote(action.selector)}${optionsString})`;
|
return asLocator(action.selector) + `.${method}(${optionsString})`;
|
||||||
}
|
}
|
||||||
case 'check':
|
case 'check':
|
||||||
return `check(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.check()`;
|
||||||
case 'uncheck':
|
case 'uncheck':
|
||||||
return `uncheck(${quote(action.selector)})`;
|
return asLocator(action.selector) + `.uncheck()`;
|
||||||
case 'fill':
|
case 'fill':
|
||||||
return `fill(${quote(action.selector)}, ${quote(action.text)})`;
|
return asLocator(action.selector) + `.fill(${quote(action.text)})`;
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `set_input_files(${quote(action.selector)}, ${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
|
return asLocator(action.selector) + `.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `press(${quote(action.selector)}, ${quote(shortcut)})`;
|
return asLocator(action.selector) + `.press(${quote(shortcut)})`;
|
||||||
}
|
}
|
||||||
case 'navigate':
|
case 'navigate':
|
||||||
return `goto(${quote(action.url)})`;
|
return `goto(${quote(action.url)})`;
|
||||||
case 'select':
|
case 'select':
|
||||||
return `select_option(${quote(action.selector)}, ${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
|
return asLocator(action.selector) + `.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,3 +280,12 @@ class PythonFormatter {
|
|||||||
function quote(text: string) {
|
function quote(text: string) {
|
||||||
return escapeWithQuotes(text, '\"');
|
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]})`;
|
||||||
|
}
|
||||||
|
|||||||
@ -121,6 +121,14 @@ export type DialogSignal = BaseSignal & {
|
|||||||
|
|
||||||
export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal;
|
export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal;
|
||||||
|
|
||||||
|
export type FrameDescription = {
|
||||||
|
pageAlias: string;
|
||||||
|
isMainFrame: boolean;
|
||||||
|
url: string;
|
||||||
|
name?: string;
|
||||||
|
selectorsChain?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export function actionTitle(action: Action): string {
|
export function actionTitle(action: Action): string {
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'openPage':
|
case 'openPage':
|
||||||
|
|||||||
@ -48,13 +48,3 @@ export function toModifiers(modifiers: number): ('Alt' | 'Control' | 'Meta' | 'S
|
|||||||
result.push('Shift');
|
result.push('Shift');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeFrame(frame: Frame): { frameName?: string, frameUrl: string, isMainFrame: boolean } {
|
|
||||||
const page = frame._page;
|
|
||||||
if (page.mainFrame() === frame)
|
|
||||||
return { isMainFrame: true, frameUrl: frame.url() };
|
|
||||||
const frames = page.frames().filter(f => f.name() === frame.name());
|
|
||||||
if (frames.length === 1 && frames[0] === frame)
|
|
||||||
return { isMainFrame: false, frameUrl: frame.url(), frameName: frame.name() };
|
|
||||||
return { isMainFrame: false, frameUrl: frame.url() };
|
|
||||||
}
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import * as fs from 'fs';
|
|||||||
import * as actions from './recorder/recorderActions';
|
import * as actions from './recorder/recorderActions';
|
||||||
import type * as channels from '../../protocol/channels';
|
import type * as channels from '../../protocol/channels';
|
||||||
import { CodeGenerator, ActionInContext } from './recorder/codeGenerator';
|
import { CodeGenerator, ActionInContext } from './recorder/codeGenerator';
|
||||||
import { describeFrame, toClickOptions, toModifiers } from './recorder/utils';
|
import { toClickOptions, toModifiers } from './recorder/utils';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { Frame } from '../frames';
|
import { Frame } from '../frames';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
@ -36,6 +36,7 @@ import { createGuid, isUnderTest, monotonicTime } from '../../utils/utils';
|
|||||||
import { metadataToCallLog } from './recorder/recorderUtils';
|
import { metadataToCallLog } from './recorder/recorderUtils';
|
||||||
import { Debugger } from './debugger';
|
import { Debugger } from './debugger';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import { raceAgainstTimeout } from '../../utils/async';
|
||||||
|
|
||||||
type BindingSource = { frame: Frame, page: Page };
|
type BindingSource = { frame: Frame, page: Page };
|
||||||
|
|
||||||
@ -380,16 +381,15 @@ class ContextRecorder extends EventEmitter {
|
|||||||
// First page is called page, others are called popup1, popup2, etc.
|
// First page is called page, others are called popup1, popup2, etc.
|
||||||
const frame = page.mainFrame();
|
const frame = page.mainFrame();
|
||||||
page.on('close', () => {
|
page.on('close', () => {
|
||||||
this._pageAliases.delete(page);
|
|
||||||
this._generator.addAction({
|
this._generator.addAction({
|
||||||
pageAlias,
|
frame: this._describeMainFrame(page),
|
||||||
...describeFrame(page.mainFrame()),
|
|
||||||
committed: true,
|
committed: true,
|
||||||
action: {
|
action: {
|
||||||
name: 'closePage',
|
name: 'closePage',
|
||||||
signals: [],
|
signals: [],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this._pageAliases.delete(page);
|
||||||
});
|
});
|
||||||
frame.on(Frame.Events.Navigation, () => this._onFrameNavigated(frame, page));
|
frame.on(Frame.Events.Navigation, () => this._onFrameNavigated(frame, page));
|
||||||
page.on(Page.Events.Download, () => this._onDownload(page));
|
page.on(Page.Events.Download, () => this._onDownload(page));
|
||||||
@ -402,8 +402,7 @@ class ContextRecorder extends EventEmitter {
|
|||||||
this._onPopup(page.opener()!, page);
|
this._onPopup(page.opener()!, page);
|
||||||
} else {
|
} else {
|
||||||
this._generator.addAction({
|
this._generator.addAction({
|
||||||
pageAlias,
|
frame: this._describeMainFrame(page),
|
||||||
...describeFrame(page.mainFrame()),
|
|
||||||
committed: true,
|
committed: true,
|
||||||
action: {
|
action: {
|
||||||
name: 'openPage',
|
name: 'openPage',
|
||||||
@ -422,14 +421,69 @@ class ContextRecorder extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _describeMainFrame(page: Page): actions.FrameDescription {
|
||||||
|
return {
|
||||||
|
pageAlias: this._pageAliases.get(page)!,
|
||||||
|
isMainFrame: true,
|
||||||
|
url: page.mainFrame().url(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _describeFrame(frame: Frame): Promise<actions.FrameDescription> {
|
||||||
|
const page = frame._page;
|
||||||
|
const pageAlias = this._pageAliases.get(page)!;
|
||||||
|
const chain: Frame[] = [];
|
||||||
|
for (let ancestor: Frame | null = frame; ancestor; ancestor = ancestor.parentFrame())
|
||||||
|
chain.push(ancestor);
|
||||||
|
chain.reverse();
|
||||||
|
|
||||||
|
if (chain.length === 1)
|
||||||
|
return this._describeMainFrame(page);
|
||||||
|
|
||||||
|
const hasUniqueName = page.frames().filter(f => f.name() === frame.name()).length === 1;
|
||||||
|
const fallback: actions.FrameDescription = {
|
||||||
|
pageAlias,
|
||||||
|
isMainFrame: false,
|
||||||
|
url: frame.url(),
|
||||||
|
name: frame.name() && hasUniqueName ? frame.name() : undefined,
|
||||||
|
};
|
||||||
|
if (chain.length > 3)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
const selectorPromises: Promise<string | undefined>[] = [];
|
||||||
|
for (let i = 0; i < chain.length - 1; i++)
|
||||||
|
selectorPromises.push(this._findFrameSelector(chain[i + 1], chain[i]));
|
||||||
|
|
||||||
|
const result = await raceAgainstTimeout(() => Promise.all(selectorPromises), 2000);
|
||||||
|
if (!result.timedOut && result.result.every(selector => !!selector)) {
|
||||||
|
return {
|
||||||
|
...fallback,
|
||||||
|
selectorsChain: result.result as string[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _findFrameSelector(frame: Frame, parent: Frame): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const frameElement = await frame.frameElement();
|
||||||
|
if (!frameElement)
|
||||||
|
return;
|
||||||
|
const utility = await parent._utilityContext();
|
||||||
|
const injected = await utility.injectedScript();
|
||||||
|
const selector = await injected.evaluate((injected, element) => injected.generateSelector(element as Element), frameElement);
|
||||||
|
return selector;
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _performAction(frame: Frame, action: actions.Action) {
|
private async _performAction(frame: Frame, action: actions.Action) {
|
||||||
// Commit last action so that no further signals are added to it.
|
// Commit last action so that no further signals are added to it.
|
||||||
this._generator.commitLastAction();
|
this._generator.commitLastAction();
|
||||||
|
|
||||||
const page = frame._page;
|
const frameDescription = await this._describeFrame(frame);
|
||||||
const actionInContext: ActionInContext = {
|
const actionInContext: ActionInContext = {
|
||||||
pageAlias: this._pageAliases.get(page)!,
|
frame: frameDescription,
|
||||||
...describeFrame(frame),
|
|
||||||
action
|
action
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -476,20 +530,20 @@ class ContextRecorder extends EventEmitter {
|
|||||||
const kActionTimeout = 5000;
|
const kActionTimeout = 5000;
|
||||||
if (action.name === 'click') {
|
if (action.name === 'click') {
|
||||||
const { options } = toClickOptions(action);
|
const { options } = toClickOptions(action);
|
||||||
await perform('click', { selector: action.selector }, callMetadata => frame.click(callMetadata, action.selector, { ...options, timeout: kActionTimeout }));
|
await perform('click', { selector: action.selector }, callMetadata => frame.click(callMetadata, action.selector, { ...options, timeout: kActionTimeout, strict: true }));
|
||||||
}
|
}
|
||||||
if (action.name === 'press') {
|
if (action.name === 'press') {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
await perform('press', { selector: action.selector, key: shortcut }, callMetadata => frame.press(callMetadata, action.selector, shortcut, { timeout: kActionTimeout }));
|
await perform('press', { selector: action.selector, key: shortcut }, callMetadata => frame.press(callMetadata, action.selector, shortcut, { timeout: kActionTimeout, strict: true }));
|
||||||
}
|
}
|
||||||
if (action.name === 'check')
|
if (action.name === 'check')
|
||||||
await perform('check', { selector: action.selector }, callMetadata => frame.check(callMetadata, action.selector, { timeout: kActionTimeout }));
|
await perform('check', { selector: action.selector }, callMetadata => frame.check(callMetadata, action.selector, { timeout: kActionTimeout, strict: true }));
|
||||||
if (action.name === 'uncheck')
|
if (action.name === 'uncheck')
|
||||||
await perform('uncheck', { selector: action.selector }, callMetadata => frame.uncheck(callMetadata, action.selector, { timeout: kActionTimeout }));
|
await perform('uncheck', { selector: action.selector }, callMetadata => frame.uncheck(callMetadata, action.selector, { timeout: kActionTimeout, strict: true }));
|
||||||
if (action.name === 'select') {
|
if (action.name === 'select') {
|
||||||
const values = action.options.map(value => ({ value }));
|
const values = action.options.map(value => ({ value }));
|
||||||
await perform('selectOption', { selector: action.selector, values }, callMetadata => frame.selectOption(callMetadata, action.selector, [], values, { timeout: kActionTimeout }));
|
await perform('selectOption', { selector: action.selector, values }, callMetadata => frame.selectOption(callMetadata, action.selector, [], values, { timeout: kActionTimeout, strict: true }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,11 +551,12 @@ class ContextRecorder extends EventEmitter {
|
|||||||
// Commit last action so that no further signals are added to it.
|
// Commit last action so that no further signals are added to it.
|
||||||
this._generator.commitLastAction();
|
this._generator.commitLastAction();
|
||||||
|
|
||||||
this._generator.addAction({
|
const frameDescription = await this._describeFrame(frame);
|
||||||
pageAlias: this._pageAliases.get(frame._page)!,
|
const actionInContext: ActionInContext = {
|
||||||
...describeFrame(frame),
|
frame: frameDescription,
|
||||||
action
|
action
|
||||||
});
|
};
|
||||||
|
this._generator.addAction(actionInContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onFrameNavigated(frame: Frame, page: Page) {
|
private _onFrameNavigated(frame: Frame, page: Page) {
|
||||||
|
|||||||
@ -36,23 +36,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
await page.click('text=Submit');`);
|
await page.locator('text=Submit').click();`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=Submit
|
# Click text=Submit
|
||||||
page.click("text=Submit")`);
|
page.locator("text=Submit").click()`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=Submit
|
# Click text=Submit
|
||||||
await page.click("text=Submit")`);
|
await page.locator("text=Submit").click()`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
page.click("text=Submit");`);
|
page.locator("text=Submit").click();`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
await page.ClickAsync("text=Submit");`);
|
await page.Locator("text=Submit").ClickAsync();`);
|
||||||
|
|
||||||
expect(message.text()).toBe('click');
|
expect(message.text()).toBe('click');
|
||||||
});
|
});
|
||||||
@ -84,7 +84,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
await page.click('text=Submit');`);
|
await page.locator('text=Submit').click();`);
|
||||||
expect(message.text()).toBe('click');
|
expect(message.text()).toBe('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Click canvas
|
// Click canvas
|
||||||
await page.click('canvas', {
|
await page.locator('canvas').click({
|
||||||
position: {
|
position: {
|
||||||
x: 250,
|
x: 250,
|
||||||
y: 250
|
y: 250
|
||||||
@ -124,20 +124,20 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click canvas
|
# Click canvas
|
||||||
page.click("canvas", position={"x":250,"y":250})`);
|
page.locator("canvas").click(position={"x":250,"y":250})`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click canvas
|
# Click canvas
|
||||||
await page.click("canvas", position={"x":250,"y":250})`);
|
await page.locator("canvas").click(position={"x":250,"y":250})`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click canvas
|
// Click canvas
|
||||||
page.click("canvas", new Page.ClickOptions()
|
page.locator("canvas").click(new Locator.ClickOptions()
|
||||||
.setPosition(250, 250));`);
|
.setPosition(250, 250));`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Click canvas
|
// Click canvas
|
||||||
await page.ClickAsync("canvas", new PageClickOptions
|
await page.Locator("canvas").ClickAsync(new LocatorClickOptions
|
||||||
{
|
{
|
||||||
Position = new Position
|
Position = new Position
|
||||||
{
|
{
|
||||||
@ -170,23 +170,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
await page.click('text=Submit');`);
|
await page.locator('text=Submit').click();`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=Submit
|
# Click text=Submit
|
||||||
page.click("text=Submit")`);
|
page.locator("text=Submit").click()`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=Submit
|
# Click text=Submit
|
||||||
await page.click("text=Submit")`);
|
await page.locator("text=Submit").click()`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
page.click("text=Submit");`);
|
page.locator("text=Submit").click();`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Click text=Submit
|
// Click text=Submit
|
||||||
await page.ClickAsync("text=Submit");`);
|
await page.Locator("text=Submit").ClickAsync();`);
|
||||||
|
|
||||||
expect(message.text()).toBe('click');
|
expect(message.text()).toBe('click');
|
||||||
});
|
});
|
||||||
@ -221,7 +221,7 @@ test.describe('cli codegen', () => {
|
|||||||
]);
|
]);
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Click text=Some long text here
|
// Click text=Some long text here
|
||||||
await page.click('text=Some long text here');`);
|
await page.locator('text=Some long text here').click();`);
|
||||||
expect(message.text()).toBe('click');
|
expect(message.text()).toBe('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -240,22 +240,22 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Fill input[name="name"]
|
// Fill input[name="name"]
|
||||||
await page.fill('input[name="name"]', 'John');`);
|
await page.locator('input[name="name"]').fill('John');`);
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Fill input[name="name"]
|
// Fill input[name="name"]
|
||||||
page.fill("input[name=\\\"name\\\"]", "John");`);
|
page.locator("input[name=\\\"name\\\"]").fill("John");`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Fill input[name="name"]
|
# Fill input[name="name"]
|
||||||
page.fill(\"input[name=\\\"name\\\"]\", \"John\")`);
|
page.locator(\"input[name=\\\"name\\\"]\").fill(\"John\")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Fill input[name="name"]
|
# Fill input[name="name"]
|
||||||
await page.fill(\"input[name=\\\"name\\\"]\", \"John\")`);
|
await page.locator(\"input[name=\\\"name\\\"]\").fill(\"John\")`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Fill input[name="name"]
|
// Fill input[name="name"]
|
||||||
await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
|
await page.Locator(\"input[name=\\\"name\\\"]\").FillAsync(\"John\");`);
|
||||||
|
|
||||||
expect(message.text()).toBe('John');
|
expect(message.text()).toBe('John');
|
||||||
});
|
});
|
||||||
@ -274,7 +274,7 @@ test.describe('cli codegen', () => {
|
|||||||
]);
|
]);
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Fill textarea[name="name"]
|
// Fill textarea[name="name"]
|
||||||
await page.fill('textarea[name="name"]', 'John');`);
|
await page.locator('textarea[name="name"]').fill('John');`);
|
||||||
expect(message.text()).toBe('John');
|
expect(message.text()).toBe('John');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -296,23 +296,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Press Enter with modifiers
|
// Press Enter with modifiers
|
||||||
await page.press('input[name="name"]', 'Shift+Enter');`);
|
await page.locator('input[name="name"]').press('Shift+Enter');`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Press Enter with modifiers
|
// Press Enter with modifiers
|
||||||
page.press("input[name=\\\"name\\\"]", "Shift+Enter");`);
|
page.locator("input[name=\\\"name\\\"]").press("Shift+Enter");`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Press Enter with modifiers
|
# Press Enter with modifiers
|
||||||
page.press(\"input[name=\\\"name\\\"]\", \"Shift+Enter\")`);
|
page.locator(\"input[name=\\\"name\\\"]\").press(\"Shift+Enter\")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Press Enter with modifiers
|
# Press Enter with modifiers
|
||||||
await page.press(\"input[name=\\\"name\\\"]\", \"Shift+Enter\")`);
|
await page.locator(\"input[name=\\\"name\\\"]\").press(\"Shift+Enter\")`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Press Enter with modifiers
|
// Press Enter with modifiers
|
||||||
await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
|
await page.Locator(\"input[name=\\\"name\\\"]\").PressAsync(\"Shift+Enter\");`);
|
||||||
|
|
||||||
expect(messages[0].text()).toBe('press');
|
expect(messages[0].text()).toBe('press');
|
||||||
});
|
});
|
||||||
@ -338,15 +338,15 @@ test.describe('cli codegen', () => {
|
|||||||
const text = recorder.sources().get('JavaScript').text;
|
const text = recorder.sources().get('JavaScript').text;
|
||||||
expect(text).toContain(`
|
expect(text).toContain(`
|
||||||
// Fill input[name="one"]
|
// Fill input[name="one"]
|
||||||
await page.fill('input[name="one"]', 'foobar123');`);
|
await page.locator('input[name="one"]').fill('foobar123');`);
|
||||||
|
|
||||||
expect(text).toContain(`
|
expect(text).toContain(`
|
||||||
// Press Tab
|
// Press Tab
|
||||||
await page.press('input[name="one"]', 'Tab');`);
|
await page.locator('input[name="one"]').press('Tab');`);
|
||||||
|
|
||||||
expect(text).toContain(`
|
expect(text).toContain(`
|
||||||
// Fill input[name="two"]
|
// Fill input[name="two"]
|
||||||
await page.fill('input[name="two"]', 'barfoo321');`);
|
await page.locator('input[name="two"]').fill('barfoo321');`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should record ArrowDown', async ({ page, openRecorder }) => {
|
test('should record ArrowDown', async ({ page, openRecorder }) => {
|
||||||
@ -368,7 +368,7 @@ test.describe('cli codegen', () => {
|
|||||||
]);
|
]);
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Press ArrowDown
|
// Press ArrowDown
|
||||||
await page.press('input[name="name"]', 'ArrowDown');`);
|
await page.locator('input[name="name"]').press('ArrowDown');`);
|
||||||
expect(messages[0].text()).toBe('press:ArrowDown');
|
expect(messages[0].text()).toBe('press:ArrowDown');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -392,7 +392,7 @@ test.describe('cli codegen', () => {
|
|||||||
]);
|
]);
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Press ArrowDown
|
// Press ArrowDown
|
||||||
await page.press('input[name="name"]', 'ArrowDown');`);
|
await page.locator('input[name="name"]').press('ArrowDown');`);
|
||||||
expect(messages.length).toBe(2);
|
expect(messages.length).toBe(2);
|
||||||
expect(messages[0].text()).toBe('down:ArrowDown');
|
expect(messages[0].text()).toBe('down:ArrowDown');
|
||||||
expect(messages[1].text()).toBe('up:ArrowDown');
|
expect(messages[1].text()).toBe('up:ArrowDown');
|
||||||
@ -414,23 +414,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Check input[name="accept"]
|
// Check input[name="accept"]
|
||||||
await page.check('input[name="accept"]');`);
|
await page.locator('input[name="accept"]').check();`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Check input[name="accept"]
|
// Check input[name="accept"]
|
||||||
page.check("input[name=\\\"accept\\\"]");`);
|
page.locator("input[name=\\\"accept\\\"]").check();`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Check input[name="accept"]
|
# Check input[name="accept"]
|
||||||
page.check(\"input[name=\\\"accept\\\"]\")`);
|
page.locator(\"input[name=\\\"accept\\\"]\").check()`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Check input[name="accept"]
|
# Check input[name="accept"]
|
||||||
await page.check(\"input[name=\\\"accept\\\"]\")`);
|
await page.locator(\"input[name=\\\"accept\\\"]\").check()`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Check input[name="accept"]
|
// Check input[name="accept"]
|
||||||
await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
|
await page.Locator(\"input[name=\\\"accept\\\"]\").CheckAsync();`);
|
||||||
|
|
||||||
expect(message.text()).toBe('true');
|
expect(message.text()).toBe('true');
|
||||||
});
|
});
|
||||||
@ -451,7 +451,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Check input[name="accept"]
|
// Check input[name="accept"]
|
||||||
await page.check('input[name="accept"]');`);
|
await page.locator('input[name="accept"]').check();`);
|
||||||
expect(message.text()).toBe('true');
|
expect(message.text()).toBe('true');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -471,23 +471,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Uncheck input[name="accept"]
|
// Uncheck input[name="accept"]
|
||||||
await page.uncheck('input[name="accept"]');`);
|
await page.locator('input[name="accept"]').uncheck();`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Uncheck input[name="accept"]
|
// Uncheck input[name="accept"]
|
||||||
page.uncheck("input[name=\\\"accept\\\"]");`);
|
page.locator("input[name=\\\"accept\\\"]").uncheck();`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Uncheck input[name="accept"]
|
# Uncheck input[name="accept"]
|
||||||
page.uncheck(\"input[name=\\\"accept\\\"]\")`);
|
page.locator(\"input[name=\\\"accept\\\"]\").uncheck()`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Uncheck input[name="accept"]
|
# Uncheck input[name="accept"]
|
||||||
await page.uncheck(\"input[name=\\\"accept\\\"]\")`);
|
await page.locator(\"input[name=\\\"accept\\\"]\").uncheck()`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Uncheck input[name="accept"]
|
// Uncheck input[name="accept"]
|
||||||
await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
|
await page.Locator(\"input[name=\\\"accept\\\"]\").UncheckAsync();`);
|
||||||
|
|
||||||
expect(message.text()).toBe('false');
|
expect(message.text()).toBe('false');
|
||||||
});
|
});
|
||||||
@ -508,23 +508,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Select 2
|
// Select 2
|
||||||
await page.selectOption('select', '2');`);
|
await page.locator('select').selectOption('2');`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Select 2
|
// Select 2
|
||||||
page.selectOption("select", "2");`);
|
page.locator("select").selectOption("2");`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Select 2
|
# Select 2
|
||||||
page.select_option(\"select\", \"2\")`);
|
page.locator(\"select\").select_option(\"2\")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Select 2
|
# Select 2
|
||||||
await page.select_option(\"select\", \"2\")`);
|
await page.locator(\"select\").select_option(\"2\")`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Select 2
|
// Select 2
|
||||||
await page.SelectOptionAsync(\"select\", new[] { \"2\" });`);
|
await page.Locator(\"select\").SelectOptionAsync(new[] { \"2\" });`);
|
||||||
|
|
||||||
expect(message.text()).toBe('2');
|
expect(message.text()).toBe('2');
|
||||||
});
|
});
|
||||||
@ -548,31 +548,31 @@ test.describe('cli codegen', () => {
|
|||||||
// Click text=link
|
// Click text=link
|
||||||
const [page1] = await Promise.all([
|
const [page1] = await Promise.all([
|
||||||
page.waitForEvent('popup'),
|
page.waitForEvent('popup'),
|
||||||
page.click('text=link')
|
page.locator('text=link').click()
|
||||||
]);`);
|
]);`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
Page page1 = page.waitForPopup(() -> {
|
Page page1 = page.waitForPopup(() -> {
|
||||||
page.click("text=link");
|
page.locator("text=link").click();
|
||||||
});`);
|
});`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=link
|
# Click text=link
|
||||||
with page.expect_popup() as popup_info:
|
with page.expect_popup() as popup_info:
|
||||||
page.click(\"text=link\")
|
page.locator(\"text=link\").click()
|
||||||
page1 = popup_info.value`);
|
page1 = popup_info.value`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=link
|
# Click text=link
|
||||||
async with page.expect_popup() as popup_info:
|
async with page.expect_popup() as popup_info:
|
||||||
await page.click(\"text=link\")
|
await page.locator(\"text=link\").click()
|
||||||
page1 = await popup_info.value`);
|
page1 = await popup_info.value`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
var page1 = await page.RunAndWaitForPopupAsync(async () =>
|
var page1 = await page.RunAndWaitForPopupAsync(async () =>
|
||||||
{
|
{
|
||||||
await page.ClickAsync(\"text=link\");
|
await page.Locator(\"text=link\").ClickAsync();
|
||||||
});`);
|
});`);
|
||||||
|
|
||||||
expect(popup.url()).toBe('about:blank');
|
expect(popup.url()).toBe('about:blank');
|
||||||
@ -593,32 +593,32 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
await page.click('text=link');
|
await page.locator('text=link').click();
|
||||||
// assert.equal(page.url(), 'about:blank#foo');`);
|
// assert.equal(page.url(), 'about:blank#foo');`);
|
||||||
|
|
||||||
expect(sources.get('Playwright Test').text).toContain(`
|
expect(sources.get('Playwright Test').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
await page.click('text=link');
|
await page.locator('text=link').click();
|
||||||
await expect(page).toHaveURL('about:blank#foo');`);
|
await expect(page).toHaveURL('about:blank#foo');`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
page.click("text=link");
|
page.locator("text=link").click();
|
||||||
// assert page.url().equals("about:blank#foo");`);
|
// assert page.url().equals("about:blank#foo");`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=link
|
# Click text=link
|
||||||
page.click(\"text=link\")
|
page.locator(\"text=link\").click()
|
||||||
# assert page.url == \"about:blank#foo\"`);
|
# assert page.url == \"about:blank#foo\"`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=link
|
# Click text=link
|
||||||
await page.click(\"text=link\")
|
await page.locator(\"text=link\").click()
|
||||||
# assert page.url == \"about:blank#foo\"`);
|
# assert page.url == \"about:blank#foo\"`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
await page.ClickAsync(\"text=link\");
|
await page.Locator(\"text=link\").ClickAsync();
|
||||||
// Assert.AreEqual(\"about:blank#foo\", page.Url);`);
|
// Assert.AreEqual(\"about:blank#foo\", page.Url);`);
|
||||||
|
|
||||||
expect(page.url()).toContain('about:blank#foo');
|
expect(page.url()).toContain('about:blank#foo');
|
||||||
@ -643,33 +643,33 @@ test.describe('cli codegen', () => {
|
|||||||
// Click text=link
|
// Click text=link
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(/*{ url: 'about:blank#foo' }*/),
|
page.waitForNavigation(/*{ url: 'about:blank#foo' }*/),
|
||||||
page.click('text=link')
|
page.locator('text=link').click()
|
||||||
]);`);
|
]);`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
// page.waitForNavigation(new Page.WaitForNavigationOptions().setUrl("about:blank#foo"), () ->
|
// page.waitForNavigation(new Page.WaitForNavigationOptions().setUrl("about:blank#foo"), () ->
|
||||||
page.waitForNavigation(() -> {
|
page.waitForNavigation(() -> {
|
||||||
page.click("text=link");
|
page.locator("text=link").click();
|
||||||
});`);
|
});`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=link
|
# Click text=link
|
||||||
# with page.expect_navigation(url=\"about:blank#foo\"):
|
# with page.expect_navigation(url=\"about:blank#foo\"):
|
||||||
with page.expect_navigation():
|
with page.expect_navigation():
|
||||||
page.click(\"text=link\")`);
|
page.locator(\"text=link\").click()`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=link
|
# Click text=link
|
||||||
# async with page.expect_navigation(url=\"about:blank#foo\"):
|
# async with page.expect_navigation(url=\"about:blank#foo\"):
|
||||||
async with page.expect_navigation():
|
async with page.expect_navigation():
|
||||||
await page.click(\"text=link\")`);
|
await page.locator(\"text=link\").click()`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Click text=link
|
// Click text=link
|
||||||
await page.RunAndWaitForNavigationAsync(async () =>
|
await page.RunAndWaitForNavigationAsync(async () =>
|
||||||
{
|
{
|
||||||
await page.ClickAsync(\"text=link\");
|
await page.Locator(\"text=link\").ClickAsync();
|
||||||
}/*, new PageWaitForNavigationOptions
|
}/*, new PageWaitForNavigationOptions
|
||||||
{
|
{
|
||||||
UrlString = \"about:blank#foo\"
|
UrlString = \"about:blank#foo\"
|
||||||
@ -688,8 +688,8 @@ test.describe('cli codegen', () => {
|
|||||||
await recorder.page.keyboard.insertText('@');
|
await recorder.page.keyboard.insertText('@');
|
||||||
await recorder.page.keyboard.type('example.com');
|
await recorder.page.keyboard.type('example.com');
|
||||||
await recorder.waitForOutput('JavaScript', 'example.com');
|
await recorder.waitForOutput('JavaScript', 'example.com');
|
||||||
expect(recorder.sources().get('JavaScript').text).not.toContain(`await page.press('input', 'AltGraph');`);
|
expect(recorder.sources().get('JavaScript').text).not.toContain(`await page.locator('input').press('AltGraph');`);
|
||||||
expect(recorder.sources().get('JavaScript').text).toContain(`await page.fill('input', 'playwright@example.com');`);
|
expect(recorder.sources().get('JavaScript').text).toContain(`await page.locator('input').fill('playwright@example.com');`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should middle click', async ({ page, openRecorder, server }) => {
|
test('should middle click', async ({ page, openRecorder, server }) => {
|
||||||
@ -703,22 +703,22 @@ test.describe('cli codegen', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
await page.click('text=Click me', {
|
await page.locator('text=Click me').click({
|
||||||
button: 'middle'
|
button: 'middle'
|
||||||
});`);
|
});`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
page.click("text=Click me", button="middle")`);
|
page.locator("text=Click me").click(button="middle")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
await page.click("text=Click me", button="middle")`);
|
await page.locator("text=Click me").click(button="middle")`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
page.click("text=Click me", new Page.ClickOptions()
|
page.locator("text=Click me").click(new Locator.ClickOptions()
|
||||||
.setButton(MouseButton.MIDDLE));`);
|
.setButton(MouseButton.MIDDLE));`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
await page.ClickAsync("text=Click me", new PageClickOptions
|
await page.Locator("text=Click me").ClickAsync(new LocatorClickOptions
|
||||||
{
|
{
|
||||||
Button = MouseButton.Middle,
|
Button = MouseButton.Middle,
|
||||||
});`);
|
});`);
|
||||||
|
|||||||
@ -133,23 +133,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Upload file-to-upload.txt
|
// Upload file-to-upload.txt
|
||||||
await page.setInputFiles('input[type="file"]', 'file-to-upload.txt');`);
|
await page.locator('input[type="file"]').setInputFiles('file-to-upload.txt');`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Upload file-to-upload.txt
|
// Upload file-to-upload.txt
|
||||||
page.setInputFiles("input[type=\\\"file\\\"]", Paths.get("file-to-upload.txt"));`);
|
page.locator("input[type=\\\"file\\\"]").setInputFiles(Paths.get("file-to-upload.txt"));`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Upload file-to-upload.txt
|
# Upload file-to-upload.txt
|
||||||
page.set_input_files(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\")`);
|
page.locator(\"input[type=\\\"file\\\"]\").set_input_files(\"file-to-upload.txt\")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Upload file-to-upload.txt
|
# Upload file-to-upload.txt
|
||||||
await page.set_input_files(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt\")`);
|
await page.locator(\"input[type=\\\"file\\\"]\").set_input_files(\"file-to-upload.txt\")`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Upload file-to-upload.txt
|
// Upload file-to-upload.txt
|
||||||
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\" });`);
|
await page.Locator(\"input[type=\\\"file\\\"]\").SetInputFilesAsync(new[] { \"file-to-upload.txt\" });`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should upload multiple files', async ({ page, openRecorder, browserName, asset }) => {
|
test('should upload multiple files', async ({ page, openRecorder, browserName, asset }) => {
|
||||||
@ -170,23 +170,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Upload file-to-upload.txt, file-to-upload-2.txt
|
// Upload file-to-upload.txt, file-to-upload-2.txt
|
||||||
await page.setInputFiles('input[type=\"file\"]', ['file-to-upload.txt', 'file-to-upload-2.txt']);`);
|
await page.locator('input[type=\"file\"]').setInputFiles(['file-to-upload.txt', 'file-to-upload-2.txt']);`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Upload file-to-upload.txt, file-to-upload-2.txt
|
// Upload file-to-upload.txt, file-to-upload-2.txt
|
||||||
page.setInputFiles("input[type=\\\"file\\\"]", new Path[] {Paths.get("file-to-upload.txt"), Paths.get("file-to-upload-2.txt")});`);
|
page.locator("input[type=\\\"file\\\"]").setInputFiles(new Path[] {Paths.get("file-to-upload.txt"), Paths.get("file-to-upload-2.txt")});`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Upload file-to-upload.txt, file-to-upload-2.txt
|
# Upload file-to-upload.txt, file-to-upload-2.txt
|
||||||
page.set_input_files(\"input[type=\\\"file\\\"]\", [\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
|
page.locator(\"input[type=\\\"file\\\"]\").set_input_files([\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Upload file-to-upload.txt, file-to-upload-2.txt
|
# Upload file-to-upload.txt, file-to-upload-2.txt
|
||||||
await page.set_input_files(\"input[type=\\\"file\\\"]\", [\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
|
await page.locator(\"input[type=\\\"file\\\"]\").set_input_files([\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Upload file-to-upload.txt, file-to-upload-2.txt
|
// Upload file-to-upload.txt, file-to-upload-2.txt
|
||||||
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`);
|
await page.Locator(\"input[type=\\\"file\\\"]\").SetInputFilesAsync(new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should clear files', async ({ page, openRecorder, browserName, asset }) => {
|
test('should clear files', async ({ page, openRecorder, browserName, asset }) => {
|
||||||
@ -207,23 +207,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Clear selected files
|
// Clear selected files
|
||||||
await page.setInputFiles('input[type=\"file\"]', []);`);
|
await page.locator('input[type=\"file\"]').setInputFiles([]);`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Clear selected files
|
// Clear selected files
|
||||||
page.setInputFiles("input[type=\\\"file\\\"]", new Path[0]);`);
|
page.locator("input[type=\\\"file\\\"]").setInputFiles(new Path[0]);`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Clear selected files
|
# Clear selected files
|
||||||
page.set_input_files(\"input[type=\\\"file\\\"]\", []`);
|
page.locator(\"input[type=\\\"file\\\"]\").set_input_files([])`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Clear selected files
|
# Clear selected files
|
||||||
await page.set_input_files(\"input[type=\\\"file\\\"]\", []`);
|
await page.locator(\"input[type=\\\"file\\\"]\").set_input_files([])`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Clear selected files
|
// Clear selected files
|
||||||
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
|
await page.Locator(\"input[type=\\\"file\\\"]\").SetInputFilesAsync(new[] { });`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ test.describe('cli codegen', () => {
|
|||||||
// Click text=Download
|
// Click text=Download
|
||||||
const [download] = await Promise.all([
|
const [download] = await Promise.all([
|
||||||
page.waitForEvent('download'),
|
page.waitForEvent('download'),
|
||||||
page.click('text=Download')
|
page.locator('text=Download').click()
|
||||||
]);`);
|
]);`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
@ -265,7 +265,7 @@ test.describe('cli codegen', () => {
|
|||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=Download
|
// Click text=Download
|
||||||
Download download = page.waitForDownload(() -> {
|
Download download = page.waitForDownload(() -> {
|
||||||
page.click("text=Download");
|
page.locator("text=Download").click();
|
||||||
});`);
|
});`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
@ -273,7 +273,7 @@ test.describe('cli codegen', () => {
|
|||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=Download
|
# Click text=Download
|
||||||
with page.expect_download() as download_info:
|
with page.expect_download() as download_info:
|
||||||
page.click(\"text=Download\")
|
page.locator(\"text=Download\").click()
|
||||||
download = download_info.value`);
|
download = download_info.value`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
@ -281,7 +281,7 @@ test.describe('cli codegen', () => {
|
|||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=Download
|
# Click text=Download
|
||||||
async with page.expect_download() as download_info:
|
async with page.expect_download() as download_info:
|
||||||
await page.click(\"text=Download\")
|
await page.locator(\"text=Download\").click()
|
||||||
download = await download_info.value`);
|
download = await download_info.value`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
@ -290,7 +290,7 @@ test.describe('cli codegen', () => {
|
|||||||
// Click text=Download
|
// Click text=Download
|
||||||
var download1 = await page.RunAndWaitForDownloadAsync(async () =>
|
var download1 = await page.RunAndWaitForDownloadAsync(async () =>
|
||||||
{
|
{
|
||||||
await page.ClickAsync(\"text=Download\");
|
await page.Locator(\"text=Download\").ClickAsync();
|
||||||
});`);
|
});`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -314,7 +314,7 @@ test.describe('cli codegen', () => {
|
|||||||
console.log(\`Dialog message: \${dialog.message()}\`);
|
console.log(\`Dialog message: \${dialog.message()}\`);
|
||||||
dialog.dismiss().catch(() => {});
|
dialog.dismiss().catch(() => {});
|
||||||
});
|
});
|
||||||
await page.click('text=click me');`);
|
await page.locator('text=click me').click();`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Click text=click me
|
// Click text=click me
|
||||||
@ -322,17 +322,17 @@ test.describe('cli codegen', () => {
|
|||||||
System.out.println(String.format("Dialog message: %s", dialog.message()));
|
System.out.println(String.format("Dialog message: %s", dialog.message()));
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
page.click("text=click me");`);
|
page.locator("text=click me").click();`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Click text=click me
|
# Click text=click me
|
||||||
page.once(\"dialog\", lambda dialog: dialog.dismiss())
|
page.once(\"dialog\", lambda dialog: dialog.dismiss())
|
||||||
page.click(\"text=click me\")`);
|
page.locator(\"text=click me\").click()`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Click text=click me
|
# Click text=click me
|
||||||
page.once(\"dialog\", lambda dialog: dialog.dismiss())
|
page.once(\"dialog\", lambda dialog: dialog.dismiss())
|
||||||
await page.click(\"text=click me\")`);
|
await page.locator(\"text=click me\").click()`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Click text=click me
|
// Click text=click me
|
||||||
@ -343,7 +343,7 @@ test.describe('cli codegen', () => {
|
|||||||
page.Dialog -= page_Dialog1_EventHandler;
|
page.Dialog -= page_Dialog1_EventHandler;
|
||||||
}
|
}
|
||||||
page.Dialog += page_Dialog1_EventHandler;
|
page.Dialog += page_Dialog1_EventHandler;
|
||||||
await page.ClickAsync(\"text=click me\");`);
|
await page.Locator(\"text=click me\").ClickAsync();`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -393,7 +393,7 @@ test.describe('cli codegen', () => {
|
|||||||
// Click text=link
|
// Click text=link
|
||||||
const [page1] = await Promise.all([
|
const [page1] = await Promise.all([
|
||||||
page.waitForEvent('popup'),
|
page.waitForEvent('popup'),
|
||||||
page.click('text=link', {
|
page.locator('text=link').click({
|
||||||
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
|
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
|
||||||
})
|
})
|
||||||
]);`);
|
]);`);
|
||||||
@ -423,20 +423,20 @@ test.describe('cli codegen', () => {
|
|||||||
await recorder.waitForOutput('JavaScript', 'TextB');
|
await recorder.waitForOutput('JavaScript', 'TextB');
|
||||||
|
|
||||||
const sources = recorder.sources();
|
const sources = recorder.sources();
|
||||||
expect(sources.get('JavaScript').text).toContain(`await page1.fill('input', 'TextA');`);
|
expect(sources.get('JavaScript').text).toContain(`await page1.locator('input').fill('TextA');`);
|
||||||
expect(sources.get('JavaScript').text).toContain(`await page2.fill('input', 'TextB');`);
|
expect(sources.get('JavaScript').text).toContain(`await page2.locator('input').fill('TextB');`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`page1.fill("input", "TextA");`);
|
expect(sources.get('Java').text).toContain(`page1.locator("input").fill("TextA");`);
|
||||||
expect(sources.get('Java').text).toContain(`page2.fill("input", "TextB");`);
|
expect(sources.get('Java').text).toContain(`page2.locator("input").fill("TextB");`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`page1.fill(\"input\", \"TextA\")`);
|
expect(sources.get('Python').text).toContain(`page1.locator(\"input\").fill(\"TextA\")`);
|
||||||
expect(sources.get('Python').text).toContain(`page2.fill(\"input\", \"TextB\")`);
|
expect(sources.get('Python').text).toContain(`page2.locator(\"input\").fill(\"TextB\")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`await page1.fill(\"input\", \"TextA\")`);
|
expect(sources.get('Python Async').text).toContain(`await page1.locator(\"input\").fill(\"TextA\")`);
|
||||||
expect(sources.get('Python Async').text).toContain(`await page2.fill(\"input\", \"TextB\")`);
|
expect(sources.get('Python Async').text).toContain(`await page2.locator(\"input\").fill(\"TextB\")`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`await page1.FillAsync(\"input\", \"TextA\");`);
|
expect(sources.get('C#').text).toContain(`await page1.Locator(\"input\").FillAsync(\"TextA\");`);
|
||||||
expect(sources.get('C#').text).toContain(`await page2.FillAsync(\"input\", \"TextB\");`);
|
expect(sources.get('C#').text).toContain(`await page2.Locator(\"input\").FillAsync(\"TextB\");`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('click should emit events in order', async ({ page, openRecorder }) => {
|
test('click should emit events in order', async ({ page, openRecorder }) => {
|
||||||
@ -458,7 +458,7 @@ test.describe('cli codegen', () => {
|
|||||||
});
|
});
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.click('button'),
|
page.click('button'),
|
||||||
recorder.waitForOutput('JavaScript', 'page.click')
|
recorder.waitForOutput('JavaScript', '.click(')
|
||||||
]);
|
]);
|
||||||
expect(messages).toEqual(['mousedown', 'mouseup', 'click']);
|
expect(messages).toEqual(['mousedown', 'mouseup', 'click']);
|
||||||
});
|
});
|
||||||
@ -496,99 +496,6 @@ test.describe('cli codegen', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should prefer frame name', async ({ page, openRecorder, server }) => {
|
|
||||||
const recorder = await openRecorder();
|
|
||||||
await recorder.setContentAndWait(`
|
|
||||||
<iframe src='./frames/frame.html' name='one'></iframe>
|
|
||||||
<iframe src='./frames/frame.html' name='two'></iframe>
|
|
||||||
<iframe src='./frames/frame.html'></iframe>
|
|
||||||
`, server.EMPTY_PAGE, 4);
|
|
||||||
const frameOne = page.frame({ name: 'one' });
|
|
||||||
const frameTwo = page.frame({ name: 'two' });
|
|
||||||
const otherFrame = page.frames().find(f => f !== page.mainFrame() && !f.name());
|
|
||||||
|
|
||||||
let [sources] = await Promise.all([
|
|
||||||
recorder.waitForOutput('JavaScript', 'one'),
|
|
||||||
frameOne.click('div'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.frame({
|
|
||||||
name: 'one'
|
|
||||||
}).click('text=Hi, I\\'m frame');`);
|
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
page.frame("one").click("text=Hi, I'm frame");`);
|
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
|
||||||
# Click text=Hi, I'm frame
|
|
||||||
page.frame(name=\"one\").click(\"text=Hi, I'm frame\")`);
|
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
|
||||||
# Click text=Hi, I'm frame
|
|
||||||
await page.frame(name=\"one\").click(\"text=Hi, I'm frame\")`);
|
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.Frame(\"one\").ClickAsync(\"text=Hi, I'm frame\");`);
|
|
||||||
|
|
||||||
[sources] = await Promise.all([
|
|
||||||
recorder.waitForOutput('JavaScript', 'two'),
|
|
||||||
frameTwo.click('div'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.frame({
|
|
||||||
name: 'two'
|
|
||||||
}).click('text=Hi, I\\'m frame');`);
|
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
page.frame("two").click("text=Hi, I'm frame");`);
|
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
|
||||||
# Click text=Hi, I'm frame
|
|
||||||
page.frame(name=\"two\").click(\"text=Hi, I'm frame\")`);
|
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
|
||||||
# Click text=Hi, I'm frame
|
|
||||||
await page.frame(name=\"two\").click(\"text=Hi, I'm frame\")`);
|
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.Frame(\"two\").ClickAsync(\"text=Hi, I'm frame\");`);
|
|
||||||
|
|
||||||
[sources] = await Promise.all([
|
|
||||||
recorder.waitForOutput('JavaScript', 'url: \''),
|
|
||||||
otherFrame.click('div'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.frame({
|
|
||||||
url: 'http://localhost:${server.PORT}/frames/frame.html'
|
|
||||||
}).click('text=Hi, I\\'m frame');`);
|
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
page.frameByUrl("http://localhost:${server.PORT}/frames/frame.html").click("text=Hi, I'm frame");`);
|
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
|
||||||
# Click text=Hi, I'm frame
|
|
||||||
page.frame(url=\"http://localhost:${server.PORT}/frames/frame.html\").click(\"text=Hi, I'm frame\")`);
|
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
|
||||||
# Click text=Hi, I'm frame
|
|
||||||
await page.frame(url=\"http://localhost:${server.PORT}/frames/frame.html\").click(\"text=Hi, I'm frame\")`);
|
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
|
||||||
// Click text=Hi, I'm frame
|
|
||||||
await page.FrameByUrl(\"http://localhost:${server.PORT}/frames/frame.html\").ClickAsync(\"text=Hi, I'm frame\");`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should record navigations after identical pushState', async ({ page, openRecorder, server }) => {
|
test('should record navigations after identical pushState', async ({ page, openRecorder, server }) => {
|
||||||
const recorder = await openRecorder();
|
const recorder = await openRecorder();
|
||||||
server.setRoute('/page2.html', (req, res) => {
|
server.setRoute('/page2.html', (req, res) => {
|
||||||
@ -655,22 +562,23 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
// Fill textarea[name="name"]
|
// Fill textarea[name="name"]
|
||||||
await page.fill('textarea[name="name"]', 'Hello\\'"\`\\nWorld');`);
|
await page.locator('textarea[name="name"]').fill('Hello\\'"\`\\nWorld');`);
|
||||||
|
|
||||||
expect(sources.get('Java').text).toContain(`
|
expect(sources.get('Java').text).toContain(`
|
||||||
// Fill textarea[name="name"]
|
// Fill textarea[name="name"]
|
||||||
page.fill("textarea[name=\\\"name\\\"]", "Hello'\\"\`\\nWorld");`);
|
page.locator("textarea[name=\\\"name\\\"]").fill("Hello'\\"\`\\nWorld");`);
|
||||||
|
|
||||||
expect(sources.get('Python').text).toContain(`
|
expect(sources.get('Python').text).toContain(`
|
||||||
# Fill textarea[name="name"]
|
# Fill textarea[name="name"]
|
||||||
page.fill(\"textarea[name=\\\"name\\\"]\", \"Hello'\\"\`\\nWorld\")`);
|
page.locator(\"textarea[name=\\\"name\\\"]\").fill(\"Hello'\\"\`\\nWorld\")`);
|
||||||
|
|
||||||
expect(sources.get('Python Async').text).toContain(`
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
# Fill textarea[name="name"]
|
# Fill textarea[name="name"]
|
||||||
await page.fill(\"textarea[name=\\\"name\\\"]\", \"Hello'\\"\`\\nWorld\")`);
|
await page.locator(\"textarea[name=\\\"name\\\"]\").fill(\"Hello'\\"\`\\nWorld\")`);
|
||||||
|
|
||||||
expect(sources.get('C#').text).toContain(`
|
expect(sources.get('C#').text).toContain(`
|
||||||
// Fill textarea[name="name"]
|
// Fill textarea[name="name"]
|
||||||
await page.FillAsync(\"textarea[name=\\\"name\\\"]\", \"Hello'\\"\`\\nWorld\");`);
|
await page.Locator(\"textarea[name=\\\"name\\\"]\").FillAsync(\"Hello'\\"\`\\nWorld\");`);
|
||||||
|
|
||||||
expect(message.text()).toBe('Hello\'\"\`\nWorld');
|
expect(message.text()).toBe('Hello\'\"\`\nWorld');
|
||||||
});
|
});
|
||||||
|
|||||||
235
tests/inspector/cli-codegen-3.spec.ts
Normal file
235
tests/inspector/cli-codegen-3.spec.ts
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from './inspectorTest';
|
||||||
|
|
||||||
|
test.describe('cli codegen', () => {
|
||||||
|
test.skip(({ mode }) => mode !== 'default');
|
||||||
|
|
||||||
|
test('should click locator.first', async ({ page, openRecorder }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<button onclick="console.log('click1')">Submit</button>
|
||||||
|
<button onclick="console.log('click2')">Submit</button>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const selector = await recorder.hoverOverElement('button');
|
||||||
|
expect(selector).toBe('text=Submit >> nth=0');
|
||||||
|
|
||||||
|
const [message, sources] = await Promise.all([
|
||||||
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
|
page.dispatchEvent('button', 'click', { detail: 1 })
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Click text=Submit >> nth=0
|
||||||
|
await page.locator('text=Submit').first().click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Click text=Submit >> nth=0
|
||||||
|
page.locator("text=Submit").first.click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Click text=Submit >> nth=0
|
||||||
|
await page.locator("text=Submit").first.click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Click text=Submit >> nth=0
|
||||||
|
page.locator("text=Submit").first().click();`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Click text=Submit >> nth=0
|
||||||
|
await page.Locator("text=Submit").First.ClickAsync();`);
|
||||||
|
|
||||||
|
expect(message.text()).toBe('click1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should click locator.nth', async ({ page, openRecorder }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<button onclick="console.log('click1')">Submit</button>
|
||||||
|
<button onclick="console.log('click2')">Submit</button>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const selector = await recorder.hoverOverElement('button >> nth=1');
|
||||||
|
expect(selector).toBe('text=Submit >> nth=1');
|
||||||
|
|
||||||
|
const [message, sources] = await Promise.all([
|
||||||
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
|
page.dispatchEvent('button', 'click', { detail: 1 })
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Click text=Submit >> nth=1
|
||||||
|
await page.locator('text=Submit').nth(1).click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Click text=Submit >> nth=1
|
||||||
|
page.locator("text=Submit").nth(1).click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Click text=Submit >> nth=1
|
||||||
|
await page.locator("text=Submit").nth(1).click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Click text=Submit >> nth=1
|
||||||
|
page.locator("text=Submit").nth(1).click();`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Click text=Submit >> nth=1
|
||||||
|
await page.Locator("text=Submit").Nth(1).ClickAsync();`);
|
||||||
|
|
||||||
|
expect(message.text()).toBe('click2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate frame locators', async ({ page, openRecorder, server }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
/*
|
||||||
|
iframe
|
||||||
|
div Hello1
|
||||||
|
iframe
|
||||||
|
div Hello2
|
||||||
|
iframe[name=one]
|
||||||
|
div HelloNameOne
|
||||||
|
iframe[name=two]
|
||||||
|
dev HelloNameTwo
|
||||||
|
iframe
|
||||||
|
dev HelloAnonymous
|
||||||
|
*/
|
||||||
|
await recorder.setContentAndWait(`
|
||||||
|
<iframe id=frame1 srcdoc="<div>Hello1</div><iframe srcdoc='<div>Hello2</div><iframe name=one></iframe><iframe name=two></iframe><iframe></iframe>'>">
|
||||||
|
`, server.EMPTY_PAGE, 6);
|
||||||
|
const frameHello1 = page.mainFrame().childFrames()[0];
|
||||||
|
const frameHello2 = frameHello1.childFrames()[0];
|
||||||
|
const frameOne = page.frame({ name: 'one' });
|
||||||
|
await frameOne.setContent(`<div>HelloNameOne</div>`);
|
||||||
|
const frameTwo = page.frame({ name: 'two' });
|
||||||
|
await frameTwo.setContent(`<div>HelloNameTwo</div>`);
|
||||||
|
const frameAnonymous = frameHello2.childFrames().find(f => !f.name());
|
||||||
|
await frameAnonymous.setContent(`<div>HelloNameAnonymous</div>`);
|
||||||
|
|
||||||
|
let [sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'Hello1'),
|
||||||
|
frameHello1.click('text=Hello1'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Click text=Hello1
|
||||||
|
await page.frameLocator('#frame1').locator('text=Hello1').click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Click text=Hello1
|
||||||
|
page.frameLocator("#frame1").locator("text=Hello1").click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Click text=Hello1
|
||||||
|
page.frame_locator("#frame1").locator("text=Hello1").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Click text=Hello1
|
||||||
|
await page.frame_locator("#frame1").locator("text=Hello1").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Click text=Hello1
|
||||||
|
await page.FrameLocator("#frame1").Locator("text=Hello1").ClickAsync();`);
|
||||||
|
|
||||||
|
|
||||||
|
[sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'Hello2'),
|
||||||
|
frameHello2.click('text=Hello2'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Click text=Hello2
|
||||||
|
await page.frameLocator('#frame1').frameLocator('iframe').locator('text=Hello2').click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Click text=Hello2
|
||||||
|
page.frameLocator("#frame1").frameLocator("iframe").locator("text=Hello2").click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Click text=Hello2
|
||||||
|
page.frame_locator("#frame1").frame_locator("iframe").locator("text=Hello2").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Click text=Hello2
|
||||||
|
await page.frame_locator("#frame1").frame_locator("iframe").locator("text=Hello2").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Click text=Hello2
|
||||||
|
await page.FrameLocator("#frame1").FrameLocator("iframe").Locator("text=Hello2").ClickAsync();`);
|
||||||
|
|
||||||
|
|
||||||
|
[sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'one'),
|
||||||
|
frameOne.click('text=HelloNameOne'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Click text=HelloNameOne
|
||||||
|
await page.frame({
|
||||||
|
name: 'one'
|
||||||
|
}).locator('text=HelloNameOne').click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Click text=HelloNameOne
|
||||||
|
page.frame("one").locator("text=HelloNameOne").click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Click text=HelloNameOne
|
||||||
|
page.frame(name=\"one\").locator(\"text=HelloNameOne\").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Click text=HelloNameOne
|
||||||
|
await page.frame(name=\"one\").locator(\"text=HelloNameOne\").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Click text=HelloNameOne
|
||||||
|
await page.Frame(\"one\").Locator(\"text=HelloNameOne\").ClickAsync();`);
|
||||||
|
|
||||||
|
|
||||||
|
[sources] = await Promise.all([
|
||||||
|
recorder.waitForOutput('JavaScript', 'url:'),
|
||||||
|
frameAnonymous.click('text=HelloNameAnonymous'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Click text=HelloNameAnonymous
|
||||||
|
await page.frame({
|
||||||
|
url: 'about:blank'
|
||||||
|
}).locator('text=HelloNameAnonymous').click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Click text=HelloNameAnonymous
|
||||||
|
page.frameByUrl("about:blank").locator("text=HelloNameAnonymous").click();`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Click text=HelloNameAnonymous
|
||||||
|
page.frame(url=\"about:blank\").locator(\"text=HelloNameAnonymous\").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Click text=HelloNameAnonymous
|
||||||
|
await page.frame(url=\"about:blank\").locator(\"text=HelloNameAnonymous\").click()`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Click text=HelloNameAnonymous
|
||||||
|
await page.FrameByUrl(\"about:blank\").Locator(\"text=HelloNameAnonymous\").ClickAsync();`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -115,6 +115,7 @@ it.describe('pause', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should highlight pointer', async ({ page, recorderPageGetter }) => {
|
it('should highlight pointer', async ({ page, recorderPageGetter }) => {
|
||||||
|
const actionPointPromise = waitForTestLog<{ x: number, y: number }>(page, 'Action point for test: ');
|
||||||
await page.setContent('<button>Submit</button>');
|
await page.setContent('<button>Submit</button>');
|
||||||
const scriptPromise = (async () => {
|
const scriptPromise = (async () => {
|
||||||
await page.pause();
|
await page.pause();
|
||||||
@ -123,18 +124,15 @@ it.describe('pause', () => {
|
|||||||
const recorderPage = await recorderPageGetter();
|
const recorderPage = await recorderPageGetter();
|
||||||
await recorderPage.click('[title="Step over"]');
|
await recorderPage.click('[title="Step over"]');
|
||||||
|
|
||||||
const point = await page.waitForSelector('x-pw-action-point');
|
const { x, y } = await actionPointPromise;
|
||||||
const button = await page.waitForSelector('button');
|
const button = await page.waitForSelector('button');
|
||||||
const box1 = await button.boundingBox();
|
const box1 = await button.boundingBox();
|
||||||
const box2 = await point.boundingBox();
|
|
||||||
|
|
||||||
const x1 = box1.x + box1.width / 2;
|
const x1 = box1.x + box1.width / 2;
|
||||||
const y1 = box1.y + box1.height / 2;
|
const y1 = box1.y + box1.height / 2;
|
||||||
const x2 = box2.x + box2.width / 2;
|
|
||||||
const y2 = box2.y + box2.height / 2;
|
|
||||||
|
|
||||||
expect(Math.abs(x1 - x2) < 2).toBeTruthy();
|
expect(Math.abs(x1 - x) < 2).toBeTruthy();
|
||||||
expect(Math.abs(y1 - y2) < 2).toBeTruthy();
|
expect(Math.abs(y1 - y) < 2).toBeTruthy();
|
||||||
|
|
||||||
await recorderPage.click('[title=Resume]');
|
await recorderPage.click('[title=Resume]');
|
||||||
await scriptPromise;
|
await scriptPromise;
|
||||||
@ -331,14 +329,13 @@ it.describe('pause', () => {
|
|||||||
await page.pause();
|
await page.pause();
|
||||||
})();
|
})();
|
||||||
const recorderPage = await recorderPageGetter();
|
const recorderPage = await recorderPageGetter();
|
||||||
const [element] = await Promise.all([
|
const [box1] = await Promise.all([
|
||||||
page.waitForSelector('x-pw-highlight:visible'),
|
waitForTestLog<Box>(page, 'Highlight box for test: '),
|
||||||
recorderPage.fill('input[placeholder="Playwright Selector"]', 'text=Submit'),
|
recorderPage.fill('input[placeholder="Playwright Selector"]', 'text=Submit'),
|
||||||
]);
|
]);
|
||||||
const button = await page.$('text=Submit');
|
const button = await page.$('text=Submit');
|
||||||
const box1 = await element.boundingBox();
|
|
||||||
const box2 = await button.boundingBox();
|
const box2 = await button.boundingBox();
|
||||||
expect(box1).toEqual(box2);
|
expect(roundBox(box1)).toEqual(roundBox(box2));
|
||||||
await recorderPage.click('[title=Resume]');
|
await recorderPage.click('[title=Resume]');
|
||||||
await scriptPromise;
|
await scriptPromise;
|
||||||
});
|
});
|
||||||
@ -395,3 +392,25 @@ async function sanitizeLog(recorderPage: Page): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForTestLog<T>(page: Page, prefix: string): Promise<T> {
|
||||||
|
return new Promise<T>(resolve => {
|
||||||
|
page.on('console', message => {
|
||||||
|
const text = message.text();
|
||||||
|
if (text.startsWith(prefix)) {
|
||||||
|
const json = text.substring(prefix.length);
|
||||||
|
resolve(JSON.parse(json));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type Box = { x: number, y: number, width: number, height: number };
|
||||||
|
function roundBox(box: Box): Box {
|
||||||
|
return {
|
||||||
|
x: Math.round(box.x * 1000),
|
||||||
|
y: Math.round(box.y * 1000),
|
||||||
|
width: Math.round(box.width * 1000),
|
||||||
|
height: Math.round(box.height * 1000),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user