From a0ea9b5fba7b13d482d28b474c5ef32d48acb2f8 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 22 Nov 2022 11:06:45 -0800 Subject: [PATCH] chore: simplify slowmo implementation (#18990) --- .../playwright-core/src/protocol/debug.ts | 2 ++ .../playwright-core/src/server/browser.ts | 1 + .../src/server/browserContext.ts | 11 +++++++--- .../playwright-core/src/server/debugger.ts | 21 +++++++++++++------ packages/playwright-core/src/server/dom.ts | 8 ------- packages/playwright-core/src/server/frames.ts | 11 ---------- packages/playwright-core/src/server/input.ts | 8 ------- packages/playwright-core/src/server/page.ts | 12 ----------- .../playwright-core/src/server/recorder.ts | 2 +- packages/protocol/src/protocol.yml | 4 ++++ tests/library/slowmo.spec.ts | 8 ++++--- 11 files changed, 36 insertions(+), 52 deletions(-) diff --git a/packages/playwright-core/src/protocol/debug.ts b/packages/playwright-core/src/protocol/debug.ts index b3f461b8f2b4b..cde35816c8dec 100644 --- a/packages/playwright-core/src/protocol/debug.ts +++ b/packages/playwright-core/src/protocol/debug.ts @@ -23,6 +23,7 @@ export const commandsWithTracingSnapshots = new Set([ 'WebSocket.waitForEventInfo', 'ElectronApplication.waitForEventInfo', 'AndroidDevice.waitForEventInfo', + 'Page.emulateMedia', 'Page.goBack', 'Page.goForward', 'Page.reload', @@ -90,6 +91,7 @@ export const commandsWithTracingSnapshots = new Set([ 'ElementHandle.dblclick', 'ElementHandle.dispatchEvent', 'ElementHandle.fill', + 'ElementHandle.focus', 'ElementHandle.hover', 'ElementHandle.innerHTML', 'ElementHandle.innerText', diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index ce709e315a67d..1efeee2b3361b 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -62,6 +62,7 @@ export type BrowserOptions = PlaywrightOptions & { }; export abstract class Browser extends SdkObject { + static Events = { Disconnected: 'disconnected', }; diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 7c556a998044b..a5cd5f9e0c429 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -76,6 +76,7 @@ export abstract class BrowserContext extends SdkObject { private _settingStorageState = false; readonly initScripts: string[] = []; private _routesInFlight = new Set(); + private _debugger!: Debugger; constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) { super(browser, 'browser-context'); @@ -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); }); @@ -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')); diff --git a/packages/playwright-core/src/server/debugger.ts b/packages/playwright-core/src/server/debugger.ts index 1b00174492d58..c5f2e0abe08dc 100644 --- a/packages/playwright-core/src/server/debugger.ts +++ b/packages/playwright-core/src/server/debugger.ts @@ -32,6 +32,7 @@ export class Debugger extends EventEmitter implements InstrumentationListener { PausedStateChanged: 'pausedstatechanged' }; private _muted = false; + private _slowMo: number | undefined; constructor(context: BrowserContext) { super(); @@ -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) { @@ -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 { + if (this._slowMo && shouldSlowMo(metadata)) + await this._doSlowMo(); + } + async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise { if (this._muted) return; @@ -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); +} diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 0b5979678f926..cb598086809f2 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -117,10 +117,6 @@ export class FrameExecutionContext extends js.ExecutionContext { } return this._injectedScriptPromise; } - - override async doSlowMo() { - return this.frame._page._doSlowMo(); - } } export class ElementHandle extends js.JSHandle { @@ -255,7 +251,6 @@ export class ElementHandle extends js.JSHandle { 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'> { @@ -559,7 +554,6 @@ export class ElementHandle extends js.JSHandle { 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; }); } @@ -651,7 +645,6 @@ export class ElementHandle extends js.JSHandle { else await this._page._delegate.setInputFiles(retargeted, filePayloads!); }); - await this._page._doSlowMo(); return 'done'; } @@ -659,7 +652,6 @@ export class ElementHandle extends js.JSHandle { 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); } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index acd5bc2927479..5dc10a2e46ad6 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -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; } @@ -765,24 +764,18 @@ export class Frame extends SdkObject { async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise { 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 { 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 { const context = await this._context(world); const value = await context.evaluateExpressionAndWaitForSignals(expression, isFunction, arg); - if (world === 'main') - await this._page._doSlowMo(); return value; } @@ -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 { @@ -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)); @@ -1195,7 +1186,6 @@ 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)); } @@ -1203,7 +1193,6 @@ 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._blur(progress))); - await this._page._doSlowMo(); }, this._page._timeoutSettings.timeout(options)); } diff --git a/packages/playwright-core/src/server/input.ts b/packages/playwright-core/src/server/input.ts index d049099b0048a..149c720dbb834 100644 --- a/packages/playwright-core/src/server/input.ts +++ b/packages/playwright-core/src/server/input.ts @@ -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 { @@ -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 }) { @@ -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(); } } @@ -197,7 +193,6 @@ 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 } = {}) { @@ -205,7 +200,6 @@ 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 } = {}) { @@ -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(); } } @@ -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(); } } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index e66162c7a25af..cbf176d00833d 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -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(); @@ -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)); } @@ -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)); } @@ -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)); } @@ -436,7 +426,6 @@ export class Page extends SdkObject { this._emulatedMedia.forcedColors = options.forcedColors; await this._delegate.updateEmulateMedia(); - await this._doSlowMo(); } emulatedMedia(): EmulatedMedia { @@ -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 { diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 0a8b14734fe01..449161a27e4e0 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -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(); diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index d9962f39f3038..8ad5b4a7950aa 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1218,6 +1218,8 @@ Page: - active - none - no-override + tracing: + snapshot: true exposeBinding: parameters: @@ -2365,6 +2367,8 @@ ElementHandle: pausesBeforeInput: true focus: + tracing: + snapshot: true getAttribute: parameters: diff --git a/tests/library/slowmo.spec.ts b/tests/library/slowmo.spec.ts index e4a561c543fa0..147d6b64957be 100644 --- a/tests/library/slowmo.spec.ts +++ b/tests/library/slowmo.spec.ts @@ -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);