Skip to content

Commit

Permalink
chore: simplify slowmo implementation (#18990)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Nov 22, 2022
1 parent a51c48f commit a0ea9b5
Show file tree
Hide file tree
Showing 11 changed files with 36 additions and 52 deletions.
2 changes: 2 additions & 0 deletions packages/playwright-core/src/protocol/debug.ts
Expand Up @@ -23,6 +23,7 @@ export const commandsWithTracingSnapshots = new Set([
'WebSocket.waitForEventInfo',
'ElectronApplication.waitForEventInfo',
'AndroidDevice.waitForEventInfo',
'Page.emulateMedia',
'Page.goBack',
'Page.goForward',
'Page.reload',
Expand Down Expand Up @@ -90,6 +91,7 @@ export const commandsWithTracingSnapshots = new Set([
'ElementHandle.dblclick',
'ElementHandle.dispatchEvent',
'ElementHandle.fill',
'ElementHandle.focus',
'ElementHandle.hover',
'ElementHandle.innerHTML',
'ElementHandle.innerText',
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/browser.ts
Expand Up @@ -62,6 +62,7 @@ export type BrowserOptions = PlaywrightOptions & {
};

export abstract class Browser extends SdkObject {

static Events = {
Disconnected: 'disconnected',
};
Expand Down
11 changes: 8 additions & 3 deletions packages/playwright-core/src/server/browserContext.ts
Expand Up @@ -76,6 +76,7 @@ export abstract class BrowserContext extends SdkObject {
private _settingStorageState = false;
readonly initScripts: string[] = [];
private _routesInFlight = new Set<network.Route>();
private _debugger!: Debugger;

constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) {
super(browser, 'browser-context');
Expand Down Expand Up @@ -112,16 +113,16 @@ export abstract class BrowserContext extends SdkObject {
if (this.attribution.isInternalPlaywright)
return;
// Debugger will pause execution upon page.pause in headed mode.
const contextDebugger = new Debugger(this);
this._debugger = new Debugger(this);

// When PWDEBUG=1, show inspector for each context.
if (debugMode() === 'inspector')
await Recorder.show(this, { pauseOnNextStatement: true });

// When paused, show inspector.
if (contextDebugger.isPaused())
if (this._debugger.isPaused())
Recorder.showInspector(this);
contextDebugger.on(Debugger.Events.PausedStateChanged, () => {
this._debugger.on(Debugger.Events.PausedStateChanged, () => {
Recorder.showInspector(this);
});

Expand All @@ -134,6 +135,10 @@ export abstract class BrowserContext extends SdkObject {
await this.grantPermissions(this._options.permissions);
}

debugger(): Debugger {
return this._debugger;
}

async _ensureVideosPath() {
if (this._options.recordVideo)
await mkdirIfNeeded(path.join(this._options.recordVideo.dir, 'dummy'));
Expand Down
21 changes: 15 additions & 6 deletions packages/playwright-core/src/server/debugger.ts
Expand Up @@ -32,6 +32,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
PausedStateChanged: 'pausedstatechanged'
};
private _muted = false;
private _slowMo: number | undefined;

constructor(context: BrowserContext) {
super();
Expand All @@ -44,12 +45,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
this._context.once(BrowserContext.Events.Close, () => {
this._context.instrumentation.removeListener(this);
});
}

static lookup(context?: BrowserContext): Debugger | undefined {
if (!context)
return;
return (context as any)[symbol] as Debugger | undefined;
this._slowMo = this._context._browser.options.slowMo;
}

async setMuted(muted: boolean) {
Expand All @@ -63,6 +59,15 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
await this.pause(sdkObject, metadata);
}

async _doSlowMo() {
await new Promise(f => setTimeout(f, this._slowMo));
}

async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
if (this._slowMo && shouldSlowMo(metadata))
await this._doSlowMo();
}

async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
if (this._muted)
return;
Expand Down Expand Up @@ -134,3 +139,7 @@ function shouldPauseBeforeStep(metadata: CallMetadata): boolean {
// since we stop in them on a separate instrumentation signal.
return commandsWithTracingSnapshots.has(step) && !pausesBeforeInputActions.has(metadata.type + '.' + metadata.method);
}

export function shouldSlowMo(metadata: CallMetadata): boolean {
return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method);
}
8 changes: 0 additions & 8 deletions packages/playwright-core/src/server/dom.ts
Expand Up @@ -117,10 +117,6 @@ export class FrameExecutionContext extends js.ExecutionContext {
}
return this._injectedScriptPromise;
}

override async doSlowMo() {
return this.frame._page._doSlowMo();
}
}

export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
Expand Down Expand Up @@ -255,7 +251,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
await this._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return main.evaluate(([injected, node, { type, eventInit }]) => injected.dispatchEvent(node, type, eventInit), [await main.injectedScript(), this, { type, eventInit }] as const);
});
await this._page._doSlowMo();
}

async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'> {
Expand Down Expand Up @@ -559,7 +554,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const result = await this.evaluatePoll(progress, ([injected, node, { optionsToSelect, force }]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible', 'enabled'], force, injected.selectOptions.bind(injected, optionsToSelect));
}, { optionsToSelect, force: options.force });
await this._page._doSlowMo();
return result;
});
}
Expand Down Expand Up @@ -651,15 +645,13 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
else
await this._page._delegate.setInputFiles(retargeted, filePayloads!);
});
await this._page._doSlowMo();
return 'done';
}

async focus(metadata: CallMetadata): Promise<void> {
const controller = new ProgressController(metadata, this);
await controller.run(async progress => {
const result = await this._focus(progress);
await this._page._doSlowMo();
return assertDone(throwRetargetableDOMError(result));
}, 0);
}
Expand Down
11 changes: 0 additions & 11 deletions packages/playwright-core/src/server/frames.ts
Expand Up @@ -705,7 +705,6 @@ export class Frame extends SdkObject {

const request = event.newDocument ? event.newDocument.request : undefined;
const response = request ? request._finalRequest().response() : null;
await this._page._doSlowMo();
return response;
}

Expand Down Expand Up @@ -765,24 +764,18 @@ export class Frame extends SdkObject {
async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world);
const handle = await context.evaluateExpressionHandleAndWaitForSignals(expression, isFunction, arg);
if (world === 'main')
await this._page._doSlowMo();
return handle;
}

async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world);
const value = await context.evaluateExpression(expression, isFunction, arg);
if (world === 'main')
await this._page._doSlowMo();
return value;
}

async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world);
const value = await context.evaluateExpressionAndWaitForSignals(expression, isFunction, arg);
if (world === 'main')
await this._page._doSlowMo();
return value;
}

Expand Down Expand Up @@ -833,7 +826,6 @@ export class Frame extends SdkObject {
await this._scheduleRerunnableTask(metadata, selector, (progress, element, data) => {
progress.injectedScript.dispatchEvent(element, data.type, data.eventInit);
}, { type, eventInit }, { mainWorld: true, ...options });
await this._page._doSlowMo();
}

async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
Expand Down Expand Up @@ -919,7 +911,6 @@ export class Frame extends SdkObject {
document.close();
}, { html, tag });
await Promise.all([contentPromise, lifecyclePromise]);
await this._page._doSlowMo();
return null;
});
}, this._page._timeoutSettings.navigationTimeout(options));
Expand Down Expand Up @@ -1195,15 +1186,13 @@ export class Frame extends SdkObject {
const controller = new ProgressController(metadata, this);
await controller.run(async progress => {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._focus(progress)));
await this._page._doSlowMo();
}, this._page._timeoutSettings.timeout(options));
}

async blur(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions = {}) {
const controller = new ProgressController(metadata, this);
await controller.run(async progress => {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._blur(progress)));
await this._page._doSlowMo();
}, this._page._timeoutSettings.timeout(options));
}

Expand Down
8 changes: 0 additions & 8 deletions packages/playwright-core/src/server/input.ts
Expand Up @@ -58,7 +58,6 @@ 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 {
Expand All @@ -79,12 +78,10 @@ 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 }) {
Expand Down Expand Up @@ -188,7 +185,6 @@ 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(), !!options.forClick);
await this._page._doSlowMo();
}
}

Expand All @@ -197,15 +193,13 @@ 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 } = {}) {
const { button = 'left', clickCount = 1 } = options;
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 } = {}) {
Expand Down Expand Up @@ -236,7 +230,6 @@ export class Mouse {

async wheel(deltaX: number, deltaY: number) {
await this._raw.wheel(this._x, this._y, this._buttons, this._keyboard._modifiers(), deltaX, deltaY);
await this._page._doSlowMo();
}
}

Expand Down Expand Up @@ -317,6 +310,5 @@ export class Touchscreen {
if (!this._page._browserContext._options.hasTouch)
throw new Error('hasTouch must be enabled on the browser context before using the touchscreen.');
await this._raw.tap(x, y, this._page.keyboard._modifiers());
await this._page._doSlowMo();
}
}
12 changes: 0 additions & 12 deletions packages/playwright-core/src/server/page.ts
Expand Up @@ -253,13 +253,6 @@ export class Page extends SdkObject {
]);
}

async _doSlowMo() {
const slowMo = this._browserContext._browser.options.slowMo;
if (!slowMo)
return;
await new Promise(x => setTimeout(x, slowMo));
}

_didClose() {
this._frameManager.dispose();
this._frameThrottler.dispose();
Expand Down Expand Up @@ -378,7 +371,6 @@ export class Page extends SdkObject {
this.mainFrame()._waitForNavigation(progress, true /* requiresNewDocument */, options),
this._delegate.reload(),
]);
await this._doSlowMo();
return response;
}), this._timeoutSettings.navigationTimeout(options));
}
Expand All @@ -399,7 +391,6 @@ export class Page extends SdkObject {
const response = await waitPromise;
if (error)
throw error;
await this._doSlowMo();
return response;
}), this._timeoutSettings.navigationTimeout(options));
}
Expand All @@ -420,7 +411,6 @@ export class Page extends SdkObject {
const response = await waitPromise;
if (error)
throw error;
await this._doSlowMo();
return response;
}), this._timeoutSettings.navigationTimeout(options));
}
Expand All @@ -436,7 +426,6 @@ export class Page extends SdkObject {
this._emulatedMedia.forcedColors = options.forcedColors;

await this._delegate.updateEmulateMedia();
await this._doSlowMo();
}

emulatedMedia(): EmulatedMedia {
Expand All @@ -452,7 +441,6 @@ export class Page extends SdkObject {
async setViewportSize(viewportSize: types.Size) {
this._emulatedSize = { viewport: { ...viewportSize }, screen: { ...viewportSize } };
await this._delegate.updateEmulatedViewportSize();
await this._doSlowMo();
}

viewportSize(): types.Size | null {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/recorder.ts
Expand Up @@ -86,7 +86,7 @@ export class Recorder implements InstrumentationListener {
this._contextRecorder = new ContextRecorder(context, params);
this._context = context;
this._omitCallTracking = !!params.omitCallTracking;
this._debugger = Debugger.lookup(context)!;
this._debugger = context.debugger();
this._handleSIGINT = params.handleSIGINT;
context.instrumentation.addListener(this, context);
this._currentLanguage = this._contextRecorder.languageName();
Expand Down
4 changes: 4 additions & 0 deletions packages/protocol/src/protocol.yml
Expand Up @@ -1218,6 +1218,8 @@ Page:
- active
- none
- no-override
tracing:
snapshot: true

exposeBinding:
parameters:
Expand Down Expand Up @@ -2365,6 +2367,8 @@ ElementHandle:
pausesBeforeInput: true

focus:
tracing:
snapshot: true

getAttribute:
parameters:
Expand Down
8 changes: 5 additions & 3 deletions tests/library/slowmo.spec.ts
Expand Up @@ -19,13 +19,15 @@ import { attachFrame } from '../config/utils';

async function checkSlowMo(toImpl, page, task) {
let didSlowMo = false;
const orig = toImpl(page)._doSlowMo;
toImpl(page)._doSlowMo = async function(...args) {
const contextDebugger = toImpl(page.context()).debugger();
contextDebugger._slowMo = 100;
const orig = contextDebugger._doSlowMo;
contextDebugger._doSlowMo = async () => {
if (didSlowMo)
throw new Error('already did slowmo');
await new Promise(x => setTimeout(x, 100));
didSlowMo = true;
return orig.call(this, ...args);
return orig.call(contextDebugger);
};
await task();
expect(!!didSlowMo).toBe(true);
Expand Down

0 comments on commit a0ea9b5

Please sign in to comment.