feat(slowmo): only slowmo once per user action (#3012)

This changes the behavior of slowmo to slow down user actions instead of every protocol command. This makes slowmo a lot more predictable. Without this, there is no way to set slowmo to a good value without incurring a huge delay at the start of your test when it sets things up.
This commit is contained in:
Joel Einbinder 2020-08-18 19:13:40 -07:00 committed by GitHub
parent b0667e8bc5
commit 97157520a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 339 additions and 74 deletions

View File

@ -30,12 +30,11 @@ export interface BrowserProcess {
close(): Promise<void>;
}
export type BrowserOptions = {
export type BrowserOptions = types.UIOptions & {
name: string,
downloadsPath?: string,
headful?: boolean,
persistent?: types.BrowserContextOptions, // Undefined means no persistent context.
slowMo?: number,
browserProcess: BrowserProcess,
proxy?: ProxySettings,
};

View File

@ -21,7 +21,7 @@ import { Events as CommonEvents } from '../events';
import { assert } from '../helper';
import * as network from '../network';
import { Page, PageBinding, Worker } from '../page';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { ConnectionTransport } from '../transport';
import * as types from '../types';
import { ConnectionEvents, CRConnection, CRSession } from './crConnection';
import { CRPage } from './crPage';
@ -48,7 +48,7 @@ export class CRBrowser extends BrowserBase {
private _tracingClient: CRSession | undefined;
static async connect(transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise<CRBrowser> {
const connection = new CRConnection(SlowMoTransport.wrap(transport, options.slowMo));
const connection = new CRConnection(transport);
const browser = new CRBrowser(connection, options);
browser._devtools = devtools;
const session = connection.rootSession;

View File

@ -103,6 +103,10 @@ export class FrameExecutionContext extends js.ExecutionContext {
}
return this._debugScriptPromise;
}
async doSlowMo() {
return this.frame._page._doSlowMo();
}
}
export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
@ -200,6 +204,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
async dispatchEvent(type: string, eventInit: Object = {}) {
await this._evaluateInMain(([injected, node, { type, eventInit }]) =>
injected.dispatchEvent(node, type, eventInit), { type, eventInit });
await this._page._doSlowMo();
}
async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'> {
@ -407,7 +412,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const selectOptions = [...elements, ...values];
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.throwIfAborted(); // Avoid action that has side-effects.
return throwFatalDOMError(await this._evaluateInUtility(([injected, node, selectOptions]) => injected.selectOptions(node, selectOptions), selectOptions));
const value = await this._evaluateInUtility(([injected, node, selectOptions]) => injected.selectOptions(node, selectOptions), selectOptions);
await this._page._doSlowMo();
return throwFatalDOMError(value);
});
}
@ -480,12 +487,14 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
progress.throwIfAborted(); // Avoid action that has side-effects.
await this._page._delegate.setInputFiles(this as any as ElementHandle<HTMLInputElement>, files);
});
await this._page._doSlowMo();
return 'done';
}
async focus(): Promise<void> {
return this._page._runAbortableTask(async progress => {
await this._page._runAbortableTask(async progress => {
const result = await this._focus(progress);
await this._page._doSlowMo();
return assertDone(throwRetargetableDOMError(result));
}, 0);
}

View File

@ -21,7 +21,7 @@ import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import { Page, PageBinding } from '../page';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { ConnectionTransport } from '../transport';
import * as types from '../types';
import { ConnectionEvents, FFConnection } from './ffConnection';
import { FFPage } from './ffPage';
@ -35,7 +35,7 @@ export class FFBrowser extends BrowserBase {
private _version = '';
static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise<FFBrowser> {
const connection = new FFConnection(SlowMoTransport.wrap(transport, options.slowMo));
const connection = new FFConnection(transport);
const browser = new FFBrowser(connection, options);
const promises: Promise<any>[] = [
connection.send('Browser.enable', { attachToDefaultContext: !!options.persistent }),

View File

@ -463,7 +463,9 @@ export class Frame {
await helper.waitForEvent(progress, this._eventEmitter, kAddLifecycleEvent, (e: types.LifecycleEvent) => e === waitUntil).promise;
const request = event.newDocument ? event.newDocument.request : undefined;
return request ? request._finalRequest().response() : null;
const response = request ? request._finalRequest().response() : null;
await this._page._doSlowMo();
return response;
});
}
@ -521,12 +523,16 @@ export class Frame {
async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any): Promise<any> {
const context = await this._mainContext();
return context.evaluateExpressionHandleInternal(expression, isFunction, arg);
const handle = await context.evaluateExpressionHandleInternal(expression, isFunction, arg);
await this._page._doSlowMo();
return handle;
}
async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise<any> {
const context = await this._mainContext();
return context.evaluateExpressionInternal(expression, isFunction, arg);
const value = await context.evaluateExpressionInternal(expression, isFunction, arg);
await this._page._doSlowMo();
return value;
}
async $(selector: string): Promise<dom.ElementHandle<Element> | null> {
@ -558,11 +564,12 @@ export class Frame {
async dispatchEvent(selector: string, type: string, eventInit?: Object, options: types.TimeoutOptions = {}): Promise<void> {
const info = selectors._parseSelector(selector);
const task = dom.dispatchEventTask(info, type, eventInit || {});
return this._page._runAbortableTask(async progress => {
await this._page._runAbortableTask(async progress => {
progress.log(`Dispatching "${type}" event on selector "${selector}"...`);
// Note: we always dispatch events in the main world.
await this._scheduleRerunnableTask(progress, 'main', task);
}, this._page._timeoutSettings.timeout(options));
await this._page._doSlowMo();
}
async _$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise<any> {
@ -618,6 +625,7 @@ export class Frame {
document.close();
}, { html, tag });
await Promise.all([contentPromise, lifecyclePromise]);
await this._page._doSlowMo();
});
}
@ -802,6 +810,7 @@ export class Frame {
async focus(selector: string, options: types.TimeoutOptions = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (progress, handle) => handle._focus(progress));
await this._page._doSlowMo();
}
async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<string | null> {

View File

@ -355,6 +355,11 @@ export function getFromENV(name: string) {
return value;
}
export async function doSlowMo(amount?: number) {
if (!amount)
return;
await new Promise(x => setTimeout(x, amount));
}
export async function mkdirIfNeeded(filePath: string) {
// This will harmlessly throw on windows if the dirname is the root directory.

View File

@ -17,6 +17,7 @@
import { assert } from './helper';
import * as keyboardLayout from './usKeyboardLayout';
import * as types from './types';
import type { Page } from './page';
export const keypadLocation = keyboardLayout.keypadLocation;
@ -39,12 +40,14 @@ export interface RawKeyboard {
}
export class Keyboard {
private _raw: RawKeyboard;
private _pressedModifiers = new Set<types.KeyboardModifier>();
private _pressedKeys = new Set<string>();
private _raw: RawKeyboard;
private _page: Page;
constructor(raw: RawKeyboard) {
constructor(raw: RawKeyboard, page: Page) {
this._raw = raw;
this._page = page;
}
async down(key: string) {
@ -55,6 +58,7 @@ export class Keyboard {
this._pressedModifiers.add(description.key as types.KeyboardModifier);
const text = description.text;
await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location, autoRepeat, text);
await this._page._doSlowMo();
}
private _keyDescriptionForString(keyString: string): KeyDescription {
@ -75,10 +79,12 @@ export class Keyboard {
this._pressedModifiers.delete(description.key as types.KeyboardModifier);
this._pressedKeys.delete(description.code);
await this._raw.keyup(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location);
await this._page._doSlowMo();
}
async insertText(text: string) {
await this._raw.sendText(text);
await this._page._doSlowMo();
}
async type(text: string, options?: { delay?: number }) {
@ -111,15 +117,19 @@ export class Keyboard {
}
const tokens = split(key);
const promises = [];
key = tokens[tokens.length - 1];
for (let i = 0; i < tokens.length - 1; ++i)
await this.down(tokens[i]);
await this.down(key);
if (options.delay)
promises.push(this.down(tokens[i]));
promises.push(this.down(key));
if (options.delay) {
await Promise.all(promises);
await new Promise(f => setTimeout(f, options.delay));
await this.up(key);
}
promises.push(this.up(key));
for (let i = tokens.length - 2; i >= 0; --i)
await this.up(tokens[i]);
promises.push(this.up(tokens[i]));
await Promise.all(promises);
}
async _ensureModifiers(modifiers: types.KeyboardModifier[]): Promise<types.KeyboardModifier[]> {
@ -153,16 +163,18 @@ export interface RawMouse {
}
export class Mouse {
private _raw: RawMouse;
private _keyboard: Keyboard;
private _x = 0;
private _y = 0;
private _lastButton: 'none' | types.MouseButton = 'none';
private _buttons = new Set<types.MouseButton>();
private _raw: RawMouse;
private _page: Page;
constructor(raw: RawMouse, keyboard: Keyboard) {
constructor(raw: RawMouse, page: Page) {
this._raw = raw;
this._keyboard = keyboard;
this._page = page;
this._keyboard = this._page.keyboard;
}
async move(x: number, y: number, options: { steps?: number } = {}) {
@ -175,6 +187,7 @@ export class Mouse {
const middleX = fromX + (x - fromX) * (i / steps);
const middleY = fromY + (y - fromY) * (i / steps);
await this._raw.move(middleX, middleY, this._lastButton, this._buttons, this._keyboard._modifiers());
await this._page._doSlowMo();
}
}
@ -183,6 +196,7 @@ export class Mouse {
this._lastButton = button;
this._buttons.add(button);
await this._raw.down(this._x, this._y, this._lastButton, this._buttons, this._keyboard._modifiers(), clickCount);
await this._page._doSlowMo();
}
async up(options: { button?: types.MouseButton, clickCount?: number } = {}) {
@ -190,6 +204,7 @@ export class Mouse {
this._lastButton = 'none';
this._buttons.delete(button);
await this._raw.up(this._x, this._y, button, this._buttons, this._keyboard._modifiers(), clickCount);
await this._page._doSlowMo();
}
async click(x: number, y: number, options: { delay?: number, button?: types.MouseButton, clickCount?: number } = {}) {

View File

@ -73,6 +73,10 @@ export class ExecutionContext {
createHandle(remoteObject: RemoteObject): JSHandle {
return this._delegate.createHandle(this, remoteObject);
}
async doSlowMo() {
// overrided in FrameExecutionContext
}
}
export class JSHandle<T = any> {
@ -106,8 +110,10 @@ export class JSHandle<T = any> {
return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
}
_evaluateExpression(expression: string, isFunction: boolean, returnByValue: boolean, arg: any) {
return evaluateExpression(this._context, returnByValue, expression, isFunction, this, arg);
async _evaluateExpression(expression: string, isFunction: boolean, returnByValue: boolean, arg: any) {
const value = await evaluateExpression(this._context, returnByValue, expression, isFunction, this, arg);1;
await this._context.doSlowMo();
return value;
}
async getProperty(propertyName: string): Promise<JSHandle> {

View File

@ -133,8 +133,8 @@ export class Page extends EventEmitter {
extraHTTPHeaders: null,
};
this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
this.keyboard = new input.Keyboard(delegate.rawKeyboard);
this.mouse = new input.Mouse(delegate.rawMouse, this.keyboard);
this.keyboard = new input.Keyboard(delegate.rawKeyboard, this);
this.mouse = new input.Mouse(delegate.rawMouse, this);
this._timeoutSettings = new TimeoutSettings(browserContext._timeoutSettings);
this._screenshotter = new Screenshotter(this);
this._frameManager = new frames.FrameManager(this);
@ -143,6 +143,13 @@ export class Page extends EventEmitter {
this.coverage = delegate.coverage ? delegate.coverage() : null;
}
async _doSlowMo() {
const slowMo = this._browserContext._browserBase._options.slowMo;
if (!slowMo)
return;
await new Promise(x => setTimeout(x, slowMo));
}
_didClose() {
this._frameManager.dispose();
assert(this._closedState !== 'closed', 'Page closed twice');
@ -237,7 +244,9 @@ export class Page extends EventEmitter {
async reload(options?: types.NavigateOptions): Promise<network.Response | null> {
const waitPromise = this.mainFrame().waitForNavigation(options);
await this._delegate.reload();
return waitPromise;
const response = waitPromise;
await this._doSlowMo();
return response;
}
async goBack(options?: types.NavigateOptions): Promise<network.Response | null> {
@ -247,7 +256,9 @@ export class Page extends EventEmitter {
waitPromise.catch(() => {});
return null;
}
return waitPromise;
const response = await waitPromise;
await this._doSlowMo();
return response;
}
async goForward(options?: types.NavigateOptions): Promise<network.Response | null> {
@ -257,7 +268,9 @@ export class Page extends EventEmitter {
waitPromise.catch(() => {});
return null;
}
return waitPromise;
const response = await waitPromise;
await this._doSlowMo();
return response;
}
async emulateMedia(options: { media?: types.MediaType | null, colorScheme?: types.ColorScheme | null }) {
@ -270,11 +283,13 @@ export class Page extends EventEmitter {
if (options.colorScheme !== undefined)
this._state.colorScheme = options.colorScheme;
await this._delegate.updateEmulateMedia();
await this._doSlowMo();
}
async setViewportSize(viewportSize: types.Size) {
this._state.viewportSize = { ...viewportSize };
await this._delegate.setViewportSize(this._state.viewportSize);
await this._doSlowMo();
}
viewportSize(): types.Size | null {

View File

@ -44,48 +44,6 @@ export interface ConnectionTransport {
onclose?: () => void,
}
export class SlowMoTransport implements ConnectionTransport {
private readonly _delay: number;
private readonly _delegate: ConnectionTransport;
onmessage?: (message: ProtocolResponse) => void;
onclose?: () => void;
static wrap(transport: ConnectionTransport, delay?: number): ConnectionTransport {
return delay ? new SlowMoTransport(transport, delay) : transport;
}
constructor(transport: ConnectionTransport, delay: number) {
this._delay = delay;
this._delegate = transport;
this._delegate.onmessage = this._onmessage.bind(this);
this._delegate.onclose = this._onClose.bind(this);
}
private _onmessage(message: ProtocolResponse) {
if (this.onmessage)
this.onmessage(message);
}
private _onClose() {
if (this.onclose)
this.onclose();
this._delegate.onmessage = undefined;
this._delegate.onclose = undefined;
}
send(s: ProtocolRequest) {
setTimeout(() => {
if (this._delegate.onmessage)
this._delegate.send(s);
}, this._delay);
}
close() {
this._delegate.close();
}
}
export class WebSocketTransport implements ConnectionTransport {
private _ws: WebSocket;
private _progress: Progress;

View File

@ -282,7 +282,7 @@ type LaunchOptionsBase = {
chromiumSandbox?: boolean,
slowMo?: number,
};
export type LaunchOptions = LaunchOptionsBase & {
export type LaunchOptions = LaunchOptionsBase & UIOptions & {
firefoxUserPrefs?: { [key: string]: string | number | boolean },
};
export type LaunchPersistentOptions = LaunchOptionsBase & BrowserContextOptions;
@ -333,3 +333,7 @@ export type Error = {
name: string,
stack?: string,
};
export type UIOptions = {
slowMo?: number;
};

View File

@ -21,7 +21,7 @@ import { Events } from '../events';
import { helper, RegisteredListener, assert } from '../helper';
import * as network from '../network';
import { Page, PageBinding } from '../page';
import { ConnectionTransport, SlowMoTransport } from '../transport';
import { ConnectionTransport } from '../transport';
import * as types from '../types';
import { Protocol } from './protocol';
import { kPageProxyMessageReceived, PageProxyMessageReceivedPayload, WKConnection, WKSession } from './wkConnection';
@ -38,7 +38,7 @@ export class WKBrowser extends BrowserBase {
private readonly _eventListeners: RegisteredListener[];
static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise<WKBrowser> {
const browser = new WKBrowser(SlowMoTransport.wrap(transport, options.slowMo), options);
const browser = new WKBrowser(transport, options);
const promises: Promise<any>[] = [
browser._browserSession.send('Playwright.enable'),
];

245
test/slowmo.spec.ts Normal file
View File

@ -0,0 +1,245 @@
/**
* 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 './base.fixture';
import { attachFrame } from './utils';
const {WIRE} = testOptions;
async function checkSlowMo(toImpl, page, task) {
let didSlowMo = false;
const orig = toImpl(page)._doSlowMo;
toImpl(page)._doSlowMo = async function (...args) {
if (didSlowMo)
throw new Error('already did slowmo');
await new Promise(x => setTimeout(x, 100));
didSlowMo = true;
return orig.call(this, ...args)
}
await task();
expect(!!didSlowMo).toBe(true);
}
async function checkPageSlowMo(toImpl, page, task) {
await page.setContent(`
<button>a</button>
<input type="checkbox" class="check">
<input type="checkbox" checked=true class="uncheck">
<input class="fill">
<select>
<option>foo</option>
</select>
<input type="file" class="file">
`)
await checkSlowMo(toImpl, page, task);
}
it.skip(WIRE)('Page SlowMo $$eval', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.$$eval('button', () => void 0));
});
it.skip(WIRE)('Page SlowMo $eval', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.$eval('button', () => void 0));
});
it.skip(WIRE)('Page SlowMo check', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.check('.check'));
});
it.skip(WIRE)('Page SlowMo click', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.click('button'));
});
it.skip(WIRE)('Page SlowMo dblclick', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.dblclick('button'));
});
it.skip(WIRE)('Page SlowMo dispatchEvent', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.dispatchEvent('button', 'click'));
});
it.skip(WIRE)('Page SlowMo emulateMedia', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.emulateMedia({media: 'print'}));
});
it.skip(WIRE)('Page SlowMo evaluate', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.evaluate(() => void 0));
});
it.skip(WIRE)('Page SlowMo evaluateHandle', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.evaluateHandle(() => window));
});
it.skip(WIRE)('Page SlowMo fill', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.fill('.fill', 'foo'));
});
it.skip(WIRE)('Page SlowMo focus', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.focus('button'));
});
it.skip(WIRE)('Page SlowMo goto', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.goto('about:blank'));
});
it.skip(WIRE)('Page SlowMo hover', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.hover('button'));
});
it.skip(WIRE)('Page SlowMo press', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.press('button', 'Enter'));
});
it.skip(WIRE)('Page SlowMo reload', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.reload());
});
it.skip(WIRE)('Page SlowMo setContent', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.setContent('hello world'));
});
it.skip(WIRE)('Page SlowMo selectOption', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.selectOption('select', 'foo'));
});
it.skip(WIRE)('Page SlowMo setInputFiles', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.setInputFiles('.file', []));
});
it.skip(WIRE)('Page SlowMo setViewportSize', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.setViewportSize({height: 400, width: 400}));
});
it.skip(WIRE)('Page SlowMo type', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.type('.fill', 'a'));
});
it.skip(WIRE)('Page SlowMo uncheck', async ({page, toImpl}) => {
await checkPageSlowMo(toImpl, page, () => page.uncheck('.uncheck'));
});
async function checkFrameSlowMo(toImpl, page, server, task) {
const frame = await attachFrame(page, 'frame1', server.EMPTY_PAGE);
await frame.setContent(`
<button>a</button>
<input type="checkbox" class="check">
<input type="checkbox" checked=true class="uncheck">
<input class="fill">
<select>
<option>foo</option>
</select>
<input type="file" class="file">
`)
await checkSlowMo(toImpl, page, task.bind(null, frame));
}
it.skip(WIRE)('Frame SlowMo $$eval', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.$$eval('button', () => void 0));
});
it.skip(WIRE)('Frame SlowMo $eval', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.$eval('button', () => void 0));
});
it.skip(WIRE)('Frame SlowMo check', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.check('.check'));
});
it.skip(WIRE)('Frame SlowMo click', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.click('button'));
});
it.skip(WIRE)('Frame SlowMo dblclick', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.dblclick('button'));
});
it.skip(WIRE)('Frame SlowMo dispatchEvent', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.dispatchEvent('button', 'click'));
});
it.skip(WIRE)('Frame SlowMo evaluate', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.evaluate(() => void 0));
});
it.skip(WIRE)('Frame SlowMo evaluateHandle', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.evaluateHandle(() => window));
});
it.skip(WIRE)('Frame SlowMo fill', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.fill('.fill', 'foo'));
});
it.skip(WIRE)('Frame SlowMo focus', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.focus('button'));
});
it.skip(WIRE)('Frame SlowMo goto', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.goto('about:blank'));
});
it.skip(WIRE)('Frame SlowMo hover', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.hover('button'));
});
it.skip(WIRE)('Frame SlowMo press', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.press('button', 'Enter'));
});
it.skip(WIRE)('Frame SlowMo setContent', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.setContent('hello world'));
});
it.skip(WIRE)('Frame SlowMo selectOption', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.selectOption('select', 'foo'));
});
it.skip(WIRE)('Frame SlowMo setInputFiles', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.setInputFiles('.file', []));
});
it.skip(WIRE)('Frame SlowMo type', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.type('.fill', 'a'));
});
it.skip(WIRE)('Frame SlowMo uncheck', async({page, server, toImpl}) => {
await checkFrameSlowMo(toImpl, page, server, frame => frame.uncheck('.uncheck'));
});
async function checkElementSlowMo(toImpl, page, selector, task) {
await page.setContent(`
<button>a</button>
<input type="checkbox" class="check">
<input type="checkbox" checked=true class="uncheck">
<input class="fill">
<select>
<option>foo</option>
</select>
<input type="file" class="file">
`)
const element = await page.$(selector);
await checkSlowMo(toImpl, page, task.bind(null, element));
}
it.skip(WIRE)('ElementHandle SlowMo $$eval', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'body', element => element.$$eval('button', () => void 0));
});
it.skip(WIRE)('ElementHandle SlowMo $eval', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'body', element => element.$eval('button', () => void 0));
});
it.skip(WIRE)('ElementHandle SlowMo check', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, '.check', element => element.check());
});
it.skip(WIRE)('ElementHandle SlowMo click', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.click());
});
it.skip(WIRE)('ElementHandle SlowMo dblclick', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.dblclick());
});
it.skip(WIRE)('ElementHandle SlowMo dispatchEvent', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.dispatchEvent('click'));
});
it.skip(WIRE)('ElementHandle SlowMo evaluate', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.evaluate(() => void 0));
});
it.skip(WIRE)('ElementHandle SlowMo evaluateHandle', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.evaluateHandle(() => void 0));
});
it.skip(WIRE)('ElementHandle SlowMo fill', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, '.fill', element => element.fill('foo'));
});
it.skip(WIRE)('ElementHandle SlowMo focus', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.focus());
});
it.skip(WIRE)('ElementHandle SlowMo hover', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.hover());
});
it.skip(WIRE)('ElementHandle SlowMo press', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'button', element => element.press('Enter'));
});
it.skip(WIRE)('ElementHandle SlowMo selectOption', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, 'select', element => element.selectOption('foo'));
});
it.skip(WIRE)('ElementHandle SlowMo setInputFiles', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, '.file', element => element.setInputFiles([]));
});
it.skip(WIRE)('ElementHandle SlowMo type', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, '.fill', element => element.type( 'a'));
});
it.skip(WIRE)('ElementHandle SlowMo uncheck', async ({page, toImpl}) => {
await checkElementSlowMo(toImpl, page, '.uncheck', element => element.uncheck());
});