chore: move codegen into its own folder (#32330)
This commit is contained in:
parent
888a5b53e7
commit
6f55b57e5a
|
|
@ -20,3 +20,10 @@
|
||||||
./electron/
|
./electron/
|
||||||
./firefox/
|
./firefox/
|
||||||
./webkit/
|
./webkit/
|
||||||
|
|
||||||
|
[recorder.ts]
|
||||||
|
./codegen/codeGenerator.ts
|
||||||
|
./codegen/languages.ts
|
||||||
|
|
||||||
|
[recorderRunner.ts]
|
||||||
|
./codegen/language.ts
|
||||||
|
|
|
||||||
3
packages/playwright-core/src/server/codegen/DEPS.list
Normal file
3
packages/playwright-core/src/server/codegen/DEPS.list
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[*]
|
||||||
|
../../utils/
|
||||||
|
../deviceDescriptors.ts
|
||||||
|
|
@ -15,10 +15,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import type { BrowserContextOptions, LaunchOptions } from '../../..';
|
import type { BrowserContextOptions, LaunchOptions } from '../../../types/types';
|
||||||
import type { Frame } from '../frames';
|
import type { Frame } from '../frames';
|
||||||
import type { LanguageGenerator, LanguageGeneratorOptions } from './language';
|
import type { LanguageGenerator, LanguageGeneratorOptions } from './language';
|
||||||
import type { Action, Signal, FrameDescription } from './recorderActions';
|
import type { Action, Signal } from '../recorder/recorderActions';
|
||||||
|
|
||||||
|
export type FrameDescription = {
|
||||||
|
pageAlias: string;
|
||||||
|
framePath: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ActionInContext = {
|
export type ActionInContext = {
|
||||||
frame: FrameDescription;
|
frame: FrameDescription;
|
||||||
|
|
@ -14,13 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BrowserContextOptions } from '../../..';
|
import type { BrowserContextOptions } from '../../../types/types';
|
||||||
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
|
||||||
import { sanitizeDeviceOptions, toSignalMap } from './language';
|
|
||||||
import type { ActionInContext } from './codeGenerator';
|
import type { ActionInContext } from './codeGenerator';
|
||||||
import type { Action } from './recorderActions';
|
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
||||||
import type { MouseClickOptions } from './utils';
|
import { sanitizeDeviceOptions, toClickOptions, toKeyboardModifiers, toSignalMap } from './language';
|
||||||
import { toModifiers } from './utils';
|
|
||||||
import { escapeWithQuotes, asLocator } from '../../utils';
|
import { escapeWithQuotes, asLocator } from '../../utils';
|
||||||
import { deviceDescriptors } from '../deviceDescriptors';
|
import { deviceDescriptors } from '../deviceDescriptors';
|
||||||
|
|
||||||
|
|
@ -87,7 +84,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
lines.push(this._generateActionCall(subject, action));
|
lines.push(this._generateActionCall(subject, actionInContext));
|
||||||
|
|
||||||
if (signals.download) {
|
if (signals.download) {
|
||||||
lines.unshift(`var download${signals.download.downloadAlias} = await ${pageAlias}.RunAndWaitForDownloadAsync(async () =>\n{`);
|
lines.unshift(`var download${signals.download.downloadAlias} = await ${pageAlias}.RunAndWaitForDownloadAsync(async () =>\n{`);
|
||||||
|
|
@ -105,7 +102,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateActionCall(subject: string, action: Action): string {
|
private _generateActionCall(subject: string, actionInContext: ActionInContext): string {
|
||||||
|
const action = actionInContext.action;
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'openPage':
|
case 'openPage':
|
||||||
throw Error('Not reached');
|
throw Error('Not reached');
|
||||||
|
|
@ -115,16 +113,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
let method = 'Click';
|
let method = 'Click';
|
||||||
if (action.clickCount === 2)
|
if (action.clickCount === 2)
|
||||||
method = 'DblClick';
|
method = 'DblClick';
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const options = toClickOptions(action);
|
||||||
const options: MouseClickOptions = {};
|
|
||||||
if (action.button !== 'left')
|
|
||||||
options.button = action.button;
|
|
||||||
if (modifiers.length)
|
|
||||||
options.modifiers = modifiers;
|
|
||||||
if (action.clickCount > 2)
|
|
||||||
options.clickCount = action.clickCount;
|
|
||||||
if (action.position)
|
|
||||||
options.position = action.position;
|
|
||||||
if (!Object.entries(options).length)
|
if (!Object.entries(options).length)
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.${method}Async();`;
|
return `await ${subject}.${this._asLocator(action.selector)}.${method}Async();`;
|
||||||
const optionsString = formatObject(options, ' ', 'Locator' + method + 'Options');
|
const optionsString = formatObject(options, ' ', 'Locator' + method + 'Options');
|
||||||
|
|
@ -139,7 +128,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.SetInputFilesAsync(${formatObject(action.files)});`;
|
return `await ${subject}.${this._asLocator(action.selector)}.SetInputFilesAsync(${formatObject(action.files)});`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toKeyboardModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.PressAsync(${quote(shortcut)});`;
|
return `await ${subject}.${this._asLocator(action.selector)}.PressAsync(${quote(shortcut)});`;
|
||||||
}
|
}
|
||||||
|
|
@ -14,13 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BrowserContextOptions } from '../../..';
|
import type { BrowserContextOptions } from '../../../types/types';
|
||||||
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
import type * as types from '../types';
|
||||||
import { toSignalMap } from './language';
|
|
||||||
import type { ActionInContext } from './codeGenerator';
|
import type { ActionInContext } from './codeGenerator';
|
||||||
import type { Action } from './recorderActions';
|
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
||||||
import type { MouseClickOptions } from './utils';
|
import { toClickOptions, toKeyboardModifiers, toSignalMap } from './language';
|
||||||
import { toModifiers } from './utils';
|
|
||||||
import { deviceDescriptors } from '../deviceDescriptors';
|
import { deviceDescriptors } from '../deviceDescriptors';
|
||||||
import { JavaScriptFormatter } from './javascript';
|
import { JavaScriptFormatter } from './javascript';
|
||||||
import { escapeWithQuotes, asLocator } from '../../utils';
|
import { escapeWithQuotes, asLocator } from '../../utils';
|
||||||
|
|
@ -74,7 +72,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
||||||
});`);
|
});`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let code = this._generateActionCall(subject, action, !!actionInContext.frame.framePath.length);
|
let code = this._generateActionCall(subject, actionInContext, !!actionInContext.frame.framePath.length);
|
||||||
|
|
||||||
if (signals.popup) {
|
if (signals.popup) {
|
||||||
code = `Page ${signals.popup.popupAlias} = ${pageAlias}.waitForPopup(() -> {
|
code = `Page ${signals.popup.popupAlias} = ${pageAlias}.waitForPopup(() -> {
|
||||||
|
|
@ -93,7 +91,8 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateActionCall(subject: string, action: Action, inFrameLocator: boolean): string {
|
private _generateActionCall(subject: string, actionInContext: ActionInContext, inFrameLocator: boolean): string {
|
||||||
|
const action = actionInContext.action;
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'openPage':
|
case 'openPage':
|
||||||
throw Error('Not reached');
|
throw Error('Not reached');
|
||||||
|
|
@ -103,16 +102,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
||||||
let method = 'click';
|
let method = 'click';
|
||||||
if (action.clickCount === 2)
|
if (action.clickCount === 2)
|
||||||
method = 'dblclick';
|
method = 'dblclick';
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const options = toClickOptions(action);
|
||||||
const options: MouseClickOptions = {};
|
|
||||||
if (action.button !== 'left')
|
|
||||||
options.button = action.button;
|
|
||||||
if (modifiers.length)
|
|
||||||
options.modifiers = modifiers;
|
|
||||||
if (action.clickCount > 2)
|
|
||||||
options.clickCount = action.clickCount;
|
|
||||||
if (action.position)
|
|
||||||
options.position = action.position;
|
|
||||||
const optionsText = formatClickOptions(options);
|
const optionsText = formatClickOptions(options);
|
||||||
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.${method}(${optionsText});`;
|
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.${method}(${optionsText});`;
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +115,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)});`;
|
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)});`;
|
||||||
case 'press': {
|
case 'press': {
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const modifiers = toKeyboardModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.press(${quote(shortcut)});`;
|
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.press(${quote(shortcut)});`;
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +261,7 @@ function formatContextOptions(contextOptions: BrowserContextOptions, deviceName:
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatClickOptions(options: MouseClickOptions) {
|
function formatClickOptions(options: types.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()})`);
|
||||||
|
|
@ -14,13 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BrowserContextOptions } from '../../..';
|
import type { BrowserContextOptions } from '../../../types/types';
|
||||||
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
|
||||||
import { sanitizeDeviceOptions, toSignalMap } from './language';
|
|
||||||
import type { ActionInContext } from './codeGenerator';
|
import type { ActionInContext } from './codeGenerator';
|
||||||
import type { Action } from './recorderActions';
|
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
||||||
import type { MouseClickOptions } from './utils';
|
import { sanitizeDeviceOptions, toSignalMap, toKeyboardModifiers, toClickOptions } from './language';
|
||||||
import { toModifiers } from './utils';
|
|
||||||
import { deviceDescriptors } from '../deviceDescriptors';
|
import { deviceDescriptors } from '../deviceDescriptors';
|
||||||
import { escapeWithQuotes, asLocator } from '../../utils';
|
import { escapeWithQuotes, asLocator } from '../../utils';
|
||||||
|
|
||||||
|
|
@ -68,7 +65,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
if (signals.download)
|
if (signals.download)
|
||||||
formatter.add(`const download${signals.download.downloadAlias}Promise = ${pageAlias}.waitForEvent('download');`);
|
formatter.add(`const download${signals.download.downloadAlias}Promise = ${pageAlias}.waitForEvent('download');`);
|
||||||
|
|
||||||
formatter.add(this._generateActionCall(subject, action));
|
formatter.add(this._generateActionCall(subject, actionInContext));
|
||||||
|
|
||||||
if (signals.popup)
|
if (signals.popup)
|
||||||
formatter.add(`const ${signals.popup.popupAlias} = await ${signals.popup.popupAlias}Promise;`);
|
formatter.add(`const ${signals.popup.popupAlias} = await ${signals.popup.popupAlias}Promise;`);
|
||||||
|
|
@ -78,7 +75,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateActionCall(subject: string, action: Action): string {
|
private _generateActionCall(subject: string, actionInContext: ActionInContext): string {
|
||||||
|
const action = actionInContext.action;
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'openPage':
|
case 'openPage':
|
||||||
throw Error('Not reached');
|
throw Error('Not reached');
|
||||||
|
|
@ -88,16 +86,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
let method = 'click';
|
let method = 'click';
|
||||||
if (action.clickCount === 2)
|
if (action.clickCount === 2)
|
||||||
method = 'dblclick';
|
method = 'dblclick';
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const options = toClickOptions(action);
|
||||||
const options: MouseClickOptions = {};
|
|
||||||
if (action.button !== 'left')
|
|
||||||
options.button = action.button;
|
|
||||||
if (modifiers.length)
|
|
||||||
options.modifiers = modifiers;
|
|
||||||
if (action.clickCount > 2)
|
|
||||||
options.clickCount = action.clickCount;
|
|
||||||
if (action.position)
|
|
||||||
options.position = action.position;
|
|
||||||
const optionsString = formatOptions(options, false);
|
const optionsString = formatOptions(options, false);
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.${method}(${optionsString});`;
|
return `await ${subject}.${this._asLocator(action.selector)}.${method}(${optionsString});`;
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +99,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)});`;
|
return `await ${subject}.${this._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 = toKeyboardModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `await ${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)});`;
|
return `await ${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)});`;
|
||||||
}
|
}
|
||||||
|
|
@ -16,8 +16,9 @@
|
||||||
|
|
||||||
import type { BrowserContextOptions, LaunchOptions } from '../../..';
|
import type { BrowserContextOptions, LaunchOptions } from '../../..';
|
||||||
import type { Language } from '../../utils';
|
import type { Language } from '../../utils';
|
||||||
|
import type * as actions from '../recorder/recorderActions';
|
||||||
|
import type * as types from '../types';
|
||||||
import type { ActionInContext } from './codeGenerator';
|
import type { ActionInContext } from './codeGenerator';
|
||||||
import type { Action, DialogSignal, DownloadSignal, PopupSignal } from './recorderActions';
|
|
||||||
export type { Language } from '../../utils';
|
export type { Language } from '../../utils';
|
||||||
|
|
||||||
export type LanguageGeneratorOptions = {
|
export type LanguageGeneratorOptions = {
|
||||||
|
|
@ -51,10 +52,10 @@ export function sanitizeDeviceOptions(device: any, options: BrowserContextOption
|
||||||
return cleanedOptions;
|
return cleanedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toSignalMap(action: Action) {
|
export function toSignalMap(action: actions.Action) {
|
||||||
let popup: PopupSignal | undefined;
|
let popup: actions.PopupSignal | undefined;
|
||||||
let download: DownloadSignal | undefined;
|
let download: actions.DownloadSignal | undefined;
|
||||||
let dialog: DialogSignal | undefined;
|
let dialog: actions.DialogSignal | undefined;
|
||||||
for (const signal of action.signals) {
|
for (const signal of action.signals) {
|
||||||
if (signal.name === 'popup')
|
if (signal.name === 'popup')
|
||||||
popup = signal;
|
popup = signal;
|
||||||
|
|
@ -69,3 +70,30 @@ export function toSignalMap(action: Action) {
|
||||||
dialog,
|
dialog,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toKeyboardModifiers(modifiers: number): types.SmartKeyboardModifier[] {
|
||||||
|
const result: types.SmartKeyboardModifier[] = [];
|
||||||
|
if (modifiers & 1)
|
||||||
|
result.push('Alt');
|
||||||
|
if (modifiers & 2)
|
||||||
|
result.push('ControlOrMeta');
|
||||||
|
if (modifiers & 4)
|
||||||
|
result.push('ControlOrMeta');
|
||||||
|
if (modifiers & 8)
|
||||||
|
result.push('Shift');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toClickOptions(action: actions.ClickAction): types.MouseClickOptions {
|
||||||
|
const modifiers = toKeyboardModifiers(action.modifiers);
|
||||||
|
const options: types.MouseClickOptions = {};
|
||||||
|
if (action.button !== 'left')
|
||||||
|
options.button = action.button;
|
||||||
|
if (modifiers.length)
|
||||||
|
options.modifiers = modifiers;
|
||||||
|
if (action.clickCount > 2)
|
||||||
|
options.clickCount = action.clickCount;
|
||||||
|
if (action.position)
|
||||||
|
options.position = action.position;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
37
packages/playwright-core/src/server/codegen/languages.ts
Normal file
37
packages/playwright-core/src/server/codegen/languages.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* 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 { JavaLanguageGenerator } from './java';
|
||||||
|
import { JavaScriptLanguageGenerator } from './javascript';
|
||||||
|
import { JsonlLanguageGenerator } from './jsonl';
|
||||||
|
import { CSharpLanguageGenerator } from './csharp';
|
||||||
|
import { PythonLanguageGenerator } from './python';
|
||||||
|
|
||||||
|
export function languageSet() {
|
||||||
|
return new Set([
|
||||||
|
new JavaLanguageGenerator('junit'),
|
||||||
|
new JavaLanguageGenerator('library'),
|
||||||
|
new JavaScriptLanguageGenerator(/* isPlaywrightTest */false),
|
||||||
|
new JavaScriptLanguageGenerator(/* isPlaywrightTest */true),
|
||||||
|
new PythonLanguageGenerator(/* isAsync */false, /* isPytest */true),
|
||||||
|
new PythonLanguageGenerator(/* isAsync */false, /* isPytest */false),
|
||||||
|
new PythonLanguageGenerator(/* isAsync */true, /* isPytest */false),
|
||||||
|
new CSharpLanguageGenerator('mstest'),
|
||||||
|
new CSharpLanguageGenerator('nunit'),
|
||||||
|
new CSharpLanguageGenerator('library'),
|
||||||
|
new JsonlLanguageGenerator(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
@ -14,13 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { BrowserContextOptions } from '../../..';
|
import type { BrowserContextOptions } from '../../../types/types';
|
||||||
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
|
||||||
import { sanitizeDeviceOptions, toSignalMap } from './language';
|
|
||||||
import type { ActionInContext } from './codeGenerator';
|
import type { ActionInContext } from './codeGenerator';
|
||||||
import type { Action } from './recorderActions';
|
import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language';
|
||||||
import type { MouseClickOptions } from './utils';
|
import { sanitizeDeviceOptions, toSignalMap, toKeyboardModifiers, toClickOptions } from './language';
|
||||||
import { toModifiers } from './utils';
|
|
||||||
import { escapeWithQuotes, toSnakeCase, asLocator } from '../../utils';
|
import { escapeWithQuotes, toSnakeCase, asLocator } from '../../utils';
|
||||||
import { deviceDescriptors } from '../deviceDescriptors';
|
import { deviceDescriptors } from '../deviceDescriptors';
|
||||||
|
|
||||||
|
|
@ -66,7 +63,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
if (signals.dialog)
|
if (signals.dialog)
|
||||||
formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`);
|
formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`);
|
||||||
|
|
||||||
let code = `${this._awaitPrefix}${this._generateActionCall(subject, action)}`;
|
let code = `${this._awaitPrefix}${this._generateActionCall(subject, actionInContext)}`;
|
||||||
|
|
||||||
if (signals.popup) {
|
if (signals.popup) {
|
||||||
code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info {
|
code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info {
|
||||||
|
|
@ -87,7 +84,8 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
return formatter.format();
|
return formatter.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateActionCall(subject: string, action: Action): string {
|
private _generateActionCall(subject: string, actionInContext: ActionInContext): string {
|
||||||
|
const action = actionInContext.action;
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'openPage':
|
case 'openPage':
|
||||||
throw Error('Not reached');
|
throw Error('Not reached');
|
||||||
|
|
@ -97,16 +95,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
let method = 'click';
|
let method = 'click';
|
||||||
if (action.clickCount === 2)
|
if (action.clickCount === 2)
|
||||||
method = 'dblclick';
|
method = 'dblclick';
|
||||||
const modifiers = toModifiers(action.modifiers);
|
const options = toClickOptions(action);
|
||||||
const options: MouseClickOptions = {};
|
|
||||||
if (action.button !== 'left')
|
|
||||||
options.button = action.button;
|
|
||||||
if (modifiers.length)
|
|
||||||
options.modifiers = modifiers;
|
|
||||||
if (action.clickCount > 2)
|
|
||||||
options.clickCount = action.clickCount;
|
|
||||||
if (action.position)
|
|
||||||
options.position = action.position;
|
|
||||||
const optionsString = formatOptions(options, false);
|
const optionsString = formatOptions(options, false);
|
||||||
return `${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`;
|
return `${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`;
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +108,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
case 'setInputFiles':
|
case 'setInputFiles':
|
||||||
return `${subject}.${this._asLocator(action.selector)}.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
|
return `${subject}.${this._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 = toKeyboardModifiers(action.modifiers);
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
return `${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`;
|
return `${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`;
|
||||||
}
|
}
|
||||||
|
|
@ -17,17 +17,11 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import type * as actions from './recorder/recorderActions';
|
import type * as actions from './recorder/recorderActions';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { ActionInContext } from './recorder/codeGenerator';
|
import type { ActionInContext, FrameDescription } from './codegen/codeGenerator';
|
||||||
import { CodeGenerator } from './recorder/codeGenerator';
|
import { CodeGenerator } from './codegen/codeGenerator';
|
||||||
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';
|
||||||
import { JavaLanguageGenerator } from './recorder/java';
|
|
||||||
import { JavaScriptLanguageGenerator } from './recorder/javascript';
|
|
||||||
import { JsonlLanguageGenerator } from './recorder/jsonl';
|
|
||||||
import { CSharpLanguageGenerator } from './recorder/csharp';
|
|
||||||
import { PythonLanguageGenerator } from './recorder/python';
|
|
||||||
import * as recorderSource from '../generated/recorderSource';
|
import * as recorderSource from '../generated/recorderSource';
|
||||||
import * as consoleApiSource from '../generated/consoleApiSource';
|
import * as consoleApiSource from '../generated/consoleApiSource';
|
||||||
import { EmptyRecorderApp } from './recorder/recorderApp';
|
import { EmptyRecorderApp } from './recorder/recorderApp';
|
||||||
|
|
@ -36,15 +30,17 @@ import { RecorderApp } from './recorder/recorderApp';
|
||||||
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
||||||
import type { Point } from '../common/types';
|
import type { Point } from '../common/types';
|
||||||
import type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
|
import type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
|
||||||
import { createGuid, isUnderTest, monotonicTime, serializeExpectedTextValues } from '../utils';
|
import { isUnderTest, monotonicTime } from '../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 { raceAgainstDeadline } from '../utils/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
||||||
import type { Language, LanguageGenerator } from './recorder/language';
|
import { type Language, type LanguageGenerator } from './codegen/language';
|
||||||
import { locatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser';
|
import { locatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser';
|
||||||
import { quoteCSSAttributeValue, eventsHelper, type RegisteredListener } from '../utils';
|
import { quoteCSSAttributeValue, eventsHelper, type RegisteredListener } from '../utils';
|
||||||
import type { Dialog } from './dialog';
|
import type { Dialog } from './dialog';
|
||||||
|
import { performAction } from './recorderRunner';
|
||||||
|
import { languageSet } from './codegen/languages';
|
||||||
|
|
||||||
type BindingSource = { frame: Frame, page: Page };
|
type BindingSource = { frame: Frame, page: Page };
|
||||||
|
|
||||||
|
|
@ -425,19 +421,7 @@ class ContextRecorder extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
setOutput(codegenId: string, outputFile?: string) {
|
setOutput(codegenId: string, outputFile?: string) {
|
||||||
const languages = new Set([
|
const languages = languageSet();
|
||||||
new JavaLanguageGenerator('junit'),
|
|
||||||
new JavaLanguageGenerator('library'),
|
|
||||||
new JavaScriptLanguageGenerator(/* isPlaywrightTest */false),
|
|
||||||
new JavaScriptLanguageGenerator(/* isPlaywrightTest */true),
|
|
||||||
new PythonLanguageGenerator(/* isAsync */false, /* isPytest */true),
|
|
||||||
new PythonLanguageGenerator(/* isAsync */false, /* isPytest */false),
|
|
||||||
new PythonLanguageGenerator(/* isAsync */true, /* isPytest */false),
|
|
||||||
new CSharpLanguageGenerator('mstest'),
|
|
||||||
new CSharpLanguageGenerator('nunit'),
|
|
||||||
new CSharpLanguageGenerator('library'),
|
|
||||||
new JsonlLanguageGenerator(),
|
|
||||||
]);
|
|
||||||
const primaryLanguage = [...languages].find(l => l.id === codegenId);
|
const primaryLanguage = [...languages].find(l => l.id === codegenId);
|
||||||
if (!primaryLanguage)
|
if (!primaryLanguage)
|
||||||
throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`);
|
throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`);
|
||||||
|
|
@ -530,14 +514,14 @@ class ContextRecorder extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _describeMainFrame(page: Page): actions.FrameDescription {
|
private _describeMainFrame(page: Page): FrameDescription {
|
||||||
return {
|
return {
|
||||||
pageAlias: this._pageAliases.get(page)!,
|
pageAlias: this._pageAliases.get(page)!,
|
||||||
framePath: [],
|
framePath: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _describeFrame(frame: Frame): Promise<actions.FrameDescription> {
|
private async _describeFrame(frame: Frame): Promise<FrameDescription> {
|
||||||
return {
|
return {
|
||||||
pageAlias: this._pageAliases.get(frame._page)!,
|
pageAlias: this._pageAliases.get(frame._page)!,
|
||||||
framePath: await generateFrameSelector(frame),
|
framePath: await generateFrameSelector(frame),
|
||||||
|
|
@ -690,98 +674,3 @@ async function generateFrameSelectorInParent(parent: Frame, frame: Frame): Promi
|
||||||
return `iframe[name=${quoteCSSAttributeValue(frame.name())}]`;
|
return `iframe[name=${quoteCSSAttributeValue(frame.name())}]`;
|
||||||
return `iframe[src=${quoteCSSAttributeValue(frame.url())}]`;
|
return `iframe[src=${quoteCSSAttributeValue(frame.url())}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function innerPerformAction(frame: Frame, action: string, params: any, cb: (callMetadata: CallMetadata) => Promise<any>): Promise<boolean> {
|
|
||||||
const callMetadata: CallMetadata = {
|
|
||||||
id: `call@${createGuid()}`,
|
|
||||||
apiName: 'frame.' + action,
|
|
||||||
objectId: frame.guid,
|
|
||||||
pageId: frame._page.guid,
|
|
||||||
frameId: frame.guid,
|
|
||||||
startTime: monotonicTime(),
|
|
||||||
endTime: 0,
|
|
||||||
type: 'Frame',
|
|
||||||
method: action,
|
|
||||||
params,
|
|
||||||
log: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await frame.instrumentation.onBeforeCall(frame, callMetadata);
|
|
||||||
await cb(callMetadata);
|
|
||||||
} catch (e) {
|
|
||||||
callMetadata.endTime = monotonicTime();
|
|
||||||
await frame.instrumentation.onAfterCall(frame, callMetadata);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
callMetadata.endTime = monotonicTime();
|
|
||||||
await frame.instrumentation.onAfterCall(frame, callMetadata);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function performAction(frame: Frame, action: actions.Action): Promise<boolean> {
|
|
||||||
const kActionTimeout = 5000;
|
|
||||||
if (action.name === 'click') {
|
|
||||||
const { options } = toClickOptions(action);
|
|
||||||
return await innerPerformAction(frame, 'click', { selector: action.selector }, callMetadata => frame.click(callMetadata, action.selector, { ...options, timeout: kActionTimeout, strict: true }));
|
|
||||||
}
|
|
||||||
if (action.name === 'press') {
|
|
||||||
const modifiers = toModifiers(action.modifiers);
|
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
|
||||||
return await innerPerformAction(frame, 'press', { selector: action.selector, key: shortcut }, callMetadata => frame.press(callMetadata, action.selector, shortcut, { timeout: kActionTimeout, strict: true }));
|
|
||||||
}
|
|
||||||
if (action.name === 'fill')
|
|
||||||
return await innerPerformAction(frame, 'fill', { selector: action.selector, text: action.text }, callMetadata => frame.fill(callMetadata, action.selector, action.text, { timeout: kActionTimeout, strict: true }));
|
|
||||||
if (action.name === 'setInputFiles')
|
|
||||||
return await innerPerformAction(frame, 'setInputFiles', { selector: action.selector, files: action.files }, callMetadata => frame.setInputFiles(callMetadata, action.selector, { selector: action.selector, payloads: [], timeout: kActionTimeout, strict: true }));
|
|
||||||
if (action.name === 'check')
|
|
||||||
return await innerPerformAction(frame, 'check', { selector: action.selector }, callMetadata => frame.check(callMetadata, action.selector, { timeout: kActionTimeout, strict: true }));
|
|
||||||
if (action.name === 'uncheck')
|
|
||||||
return await innerPerformAction(frame, 'uncheck', { selector: action.selector }, callMetadata => frame.uncheck(callMetadata, action.selector, { timeout: kActionTimeout, strict: true }));
|
|
||||||
if (action.name === 'select') {
|
|
||||||
const values = action.options.map(value => ({ value }));
|
|
||||||
return await innerPerformAction(frame, 'selectOption', { selector: action.selector, values }, callMetadata => frame.selectOption(callMetadata, action.selector, [], values, { timeout: kActionTimeout, strict: true }));
|
|
||||||
}
|
|
||||||
if (action.name === 'navigate')
|
|
||||||
return await innerPerformAction(frame, 'goto', { url: action.url }, callMetadata => frame.goto(callMetadata, action.url, { timeout: kActionTimeout }));
|
|
||||||
if (action.name === 'closePage')
|
|
||||||
return await innerPerformAction(frame, 'close', {}, callMetadata => frame._page.close(callMetadata));
|
|
||||||
if (action.name === 'openPage')
|
|
||||||
throw Error('Not reached');
|
|
||||||
if (action.name === 'assertChecked') {
|
|
||||||
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
|
||||||
selector: action.selector,
|
|
||||||
expression: 'to.be.checked',
|
|
||||||
isNot: !action.checked,
|
|
||||||
timeout: kActionTimeout,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (action.name === 'assertText') {
|
|
||||||
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
|
||||||
selector: action.selector,
|
|
||||||
expression: 'to.have.text',
|
|
||||||
expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }),
|
|
||||||
isNot: false,
|
|
||||||
timeout: kActionTimeout,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (action.name === 'assertValue') {
|
|
||||||
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
|
||||||
selector: action.selector,
|
|
||||||
expression: 'to.have.value',
|
|
||||||
expectedValue: action.value,
|
|
||||||
isNot: false,
|
|
||||||
timeout: kActionTimeout,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (action.name === 'assertVisible') {
|
|
||||||
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
|
||||||
selector: action.selector,
|
|
||||||
expression: 'to.be.visible',
|
|
||||||
isNot: false,
|
|
||||||
timeout: kActionTimeout,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
throw new Error('Internal error: unexpected action ' + (action as any).name);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -149,8 +149,3 @@ export type DialogSignal = BaseSignal & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal;
|
export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal;
|
||||||
|
|
||||||
export type FrameDescription = {
|
|
||||||
pageAlias: string;
|
|
||||||
framePath: string[];
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 type { Frame } from '../frames';
|
|
||||||
import type { SmartKeyboardModifier } from '../types';
|
|
||||||
import type * as actions from './recorderActions';
|
|
||||||
|
|
||||||
export type MouseClickOptions = Parameters<Frame['click']>[2];
|
|
||||||
|
|
||||||
export function toClickOptions(action: actions.ClickAction): { method: 'click' | 'dblclick', options: MouseClickOptions } {
|
|
||||||
let method: 'click' | 'dblclick' = 'click';
|
|
||||||
if (action.clickCount === 2)
|
|
||||||
method = 'dblclick';
|
|
||||||
const modifiers = toModifiers(action.modifiers);
|
|
||||||
const options: MouseClickOptions = {};
|
|
||||||
if (action.button !== 'left')
|
|
||||||
options.button = action.button;
|
|
||||||
if (modifiers.length)
|
|
||||||
options.modifiers = modifiers;
|
|
||||||
if (action.clickCount > 2)
|
|
||||||
options.clickCount = action.clickCount;
|
|
||||||
if (action.position)
|
|
||||||
options.position = action.position;
|
|
||||||
return { method, options };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toModifiers(modifiers: number): SmartKeyboardModifier[] {
|
|
||||||
const result: SmartKeyboardModifier[] = [];
|
|
||||||
if (modifiers & 1)
|
|
||||||
result.push('Alt');
|
|
||||||
if (modifiers & 2)
|
|
||||||
result.push('ControlOrMeta');
|
|
||||||
if (modifiers & 4)
|
|
||||||
result.push('ControlOrMeta');
|
|
||||||
if (modifiers & 8)
|
|
||||||
result.push('Shift');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
116
packages/playwright-core/src/server/recorderRunner.ts
Normal file
116
packages/playwright-core/src/server/recorderRunner.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* 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 { createGuid, monotonicTime, serializeExpectedTextValues } from '../utils';
|
||||||
|
import { toClickOptions, toKeyboardModifiers } from './codegen/language';
|
||||||
|
import type { Frame } from './frames';
|
||||||
|
import type { CallMetadata } from './instrumentation';
|
||||||
|
import type * as actions from './recorder/recorderActions';
|
||||||
|
|
||||||
|
async function innerPerformAction(frame: Frame, action: string, params: any, cb: (callMetadata: CallMetadata) => Promise<any>): Promise<boolean> {
|
||||||
|
const callMetadata: CallMetadata = {
|
||||||
|
id: `call@${createGuid()}`,
|
||||||
|
apiName: 'frame.' + action,
|
||||||
|
objectId: frame.guid,
|
||||||
|
pageId: frame._page.guid,
|
||||||
|
frameId: frame.guid,
|
||||||
|
startTime: monotonicTime(),
|
||||||
|
endTime: 0,
|
||||||
|
type: 'Frame',
|
||||||
|
method: action,
|
||||||
|
params,
|
||||||
|
log: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await frame.instrumentation.onBeforeCall(frame, callMetadata);
|
||||||
|
await cb(callMetadata);
|
||||||
|
} catch (e) {
|
||||||
|
callMetadata.endTime = monotonicTime();
|
||||||
|
await frame.instrumentation.onAfterCall(frame, callMetadata);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
callMetadata.endTime = monotonicTime();
|
||||||
|
await frame.instrumentation.onAfterCall(frame, callMetadata);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function performAction(frame: Frame, action: actions.Action): Promise<boolean> {
|
||||||
|
const kActionTimeout = 5000;
|
||||||
|
if (action.name === 'click') {
|
||||||
|
const options = toClickOptions(action);
|
||||||
|
return await innerPerformAction(frame, 'click', { selector: action.selector }, callMetadata => frame.click(callMetadata, action.selector, { ...options, timeout: kActionTimeout, strict: true }));
|
||||||
|
}
|
||||||
|
if (action.name === 'press') {
|
||||||
|
const modifiers = toKeyboardModifiers(action.modifiers);
|
||||||
|
const shortcut = [...modifiers, action.key].join('+');
|
||||||
|
return await innerPerformAction(frame, 'press', { selector: action.selector, key: shortcut }, callMetadata => frame.press(callMetadata, action.selector, shortcut, { timeout: kActionTimeout, strict: true }));
|
||||||
|
}
|
||||||
|
if (action.name === 'fill')
|
||||||
|
return await innerPerformAction(frame, 'fill', { selector: action.selector, text: action.text }, callMetadata => frame.fill(callMetadata, action.selector, action.text, { timeout: kActionTimeout, strict: true }));
|
||||||
|
if (action.name === 'setInputFiles')
|
||||||
|
return await innerPerformAction(frame, 'setInputFiles', { selector: action.selector, files: action.files }, callMetadata => frame.setInputFiles(callMetadata, action.selector, { selector: action.selector, payloads: [], timeout: kActionTimeout, strict: true }));
|
||||||
|
if (action.name === 'check')
|
||||||
|
return await innerPerformAction(frame, 'check', { selector: action.selector }, callMetadata => frame.check(callMetadata, action.selector, { timeout: kActionTimeout, strict: true }));
|
||||||
|
if (action.name === 'uncheck')
|
||||||
|
return await innerPerformAction(frame, 'uncheck', { selector: action.selector }, callMetadata => frame.uncheck(callMetadata, action.selector, { timeout: kActionTimeout, strict: true }));
|
||||||
|
if (action.name === 'select') {
|
||||||
|
const values = action.options.map(value => ({ value }));
|
||||||
|
return await innerPerformAction(frame, 'selectOption', { selector: action.selector, values }, callMetadata => frame.selectOption(callMetadata, action.selector, [], values, { timeout: kActionTimeout, strict: true }));
|
||||||
|
}
|
||||||
|
if (action.name === 'navigate')
|
||||||
|
return await innerPerformAction(frame, 'goto', { url: action.url }, callMetadata => frame.goto(callMetadata, action.url, { timeout: kActionTimeout }));
|
||||||
|
if (action.name === 'closePage')
|
||||||
|
return await innerPerformAction(frame, 'close', {}, callMetadata => frame._page.close(callMetadata));
|
||||||
|
if (action.name === 'openPage')
|
||||||
|
throw Error('Not reached');
|
||||||
|
if (action.name === 'assertChecked') {
|
||||||
|
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
||||||
|
selector: action.selector,
|
||||||
|
expression: 'to.be.checked',
|
||||||
|
isNot: !action.checked,
|
||||||
|
timeout: kActionTimeout,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (action.name === 'assertText') {
|
||||||
|
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
||||||
|
selector: action.selector,
|
||||||
|
expression: 'to.have.text',
|
||||||
|
expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }),
|
||||||
|
isNot: false,
|
||||||
|
timeout: kActionTimeout,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (action.name === 'assertValue') {
|
||||||
|
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
||||||
|
selector: action.selector,
|
||||||
|
expression: 'to.have.value',
|
||||||
|
expectedValue: action.value,
|
||||||
|
isNot: false,
|
||||||
|
timeout: kActionTimeout,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (action.name === 'assertVisible') {
|
||||||
|
return await innerPerformAction(frame, 'expect', { selector: action.selector }, callMetadata => frame.expect(callMetadata, action.selector, {
|
||||||
|
selector: action.selector,
|
||||||
|
expression: 'to.be.visible',
|
||||||
|
isNot: false,
|
||||||
|
timeout: kActionTimeout,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
throw new Error('Internal error: unexpected action ' + (action as any).name);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue