chore: simplify slowmo implementation (#18990)
This commit is contained in:
parent
a51c48fcb1
commit
a0ea9b5fba
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export type BrowserOptions = PlaywrightOptions & {
|
|||
};
|
||||
|
||||
export abstract class Browser extends SdkObject {
|
||||
|
||||
static Events = {
|
||||
Disconnected: 'disconnected',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -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'));
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
if (this._slowMo && shouldSlowMo(metadata))
|
||||
await this._doSlowMo();
|
||||
}
|
||||
|
||||
async onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
@ -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'> {
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
@ -651,7 +645,6 @@ 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';
|
||||
}
|
||||
|
||||
|
|
@ -659,7 +652,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
|
|
@ -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> {
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue