From ca2e7ef199bbb9754bd4d70b925ff0b101df51c4 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 10 Nov 2022 12:15:29 -0800 Subject: [PATCH] chore: report paused signal to the debug controller clients (#18701) --- .../playwright-core/src/protocol/validator.ts | 5 +++++ .../src/server/debugController.ts | 14 ++++++++++++- .../playwright-core/src/server/debugger.ts | 3 +++ .../dispatchers/debugControllerDispatcher.ts | 7 +++++++ .../playwright-core/src/server/recorder.ts | 20 +++++++++++++------ packages/protocol/src/channels.ts | 9 +++++++++ packages/protocol/src/protocol.yml | 7 ++++++- tests/config/debugControllerBackend.ts | 8 ++++++++ tests/library/debug-controller.spec.ts | 14 +++++++++++++ 9 files changed, 79 insertions(+), 8 deletions(-) diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 85b34a34d2..fc69fe086b 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -344,6 +344,9 @@ scheme.DebugControllerSourceChangedEvent = tObject({ footer: tOptional(tString), actions: tOptional(tArray(tString)), }); +scheme.DebugControllerPausedEvent = tObject({ + paused: tBoolean, +}); scheme.DebugControllerBrowsersChangedEvent = tObject({ browsers: tArray(tObject({ contexts: tArray(tObject({ @@ -377,6 +380,8 @@ scheme.DebugControllerHighlightParams = tObject({ scheme.DebugControllerHighlightResult = tOptional(tObject({})); scheme.DebugControllerHideHighlightParams = tOptional(tObject({})); scheme.DebugControllerHideHighlightResult = tOptional(tObject({})); +scheme.DebugControllerResumeParams = tOptional(tObject({})); +scheme.DebugControllerResumeResult = tOptional(tObject({})); scheme.DebugControllerKillParams = tOptional(tObject({})); scheme.DebugControllerKillResult = tOptional(tObject({})); scheme.DebugControllerCloseAllBrowsersParams = tOptional(tObject({})); diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 9c92e3901b..af80750724 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -34,6 +34,7 @@ export class DebugController extends SdkObject { StateChanged: 'stateChanged', InspectRequested: 'inspectRequested', SourceChanged: 'sourceChanged', + Paused: 'paused', }; private _autoCloseTimer: NodeJS.Timeout | undefined; @@ -52,6 +53,7 @@ export class DebugController extends SdkObject { initialize(codegenId: string, sdkLanguage: Language) { this._codegenId = codegenId; this._sdkLanguage = sdkLanguage; + Recorder.setAppFactory(async () => new InspectingRecorderApp(this)); } setAutoCloseAllowed(allowed: boolean) { @@ -61,6 +63,7 @@ export class DebugController extends SdkObject { dispose() { this.setReportStateChanged(false); this.setAutoCloseAllowed(false); + Recorder.setAppFactory(undefined); } setReportStateChanged(enabled: boolean) { @@ -157,6 +160,11 @@ export class DebugController extends SdkObject { return [...this._playwright.allBrowsers()]; } + async resume() { + for (const recorder of await this._allRecorders()) + recorder.resume(); + } + async kill() { selfDestruct(); } @@ -192,7 +200,7 @@ export class DebugController extends SdkObject { const contexts = new Set(); for (const page of this._playwright.allPages()) contexts.add(page.context()); - const result = await Promise.all([...contexts].map(c => Recorder.show(c, { omitCallTracking: true }, () => Promise.resolve(new InspectingRecorderApp(this))))); + const result = await Promise.all([...contexts].map(c => Recorder.show(c, { omitCallTracking: true }))); return result.filter(Boolean) as Recorder[]; } @@ -235,4 +243,8 @@ class InspectingRecorderApp extends EmptyRecorderApp { const { text, header, footer, actions } = source || { text: '' }; this._debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions }); } + + override async setPaused(paused: boolean) { + this._debugController.emit(DebugController.Events.Paused, { paused }); + } } diff --git a/packages/playwright-core/src/server/debugger.ts b/packages/playwright-core/src/server/debugger.ts index 22a0a45466..1b00174492 100644 --- a/packages/playwright-core/src/server/debugger.ts +++ b/packages/playwright-core/src/server/debugger.ts @@ -83,6 +83,9 @@ export class Debugger extends EventEmitter implements InstrumentationListener { } resume(step: boolean) { + if (!this.isPaused()) + return; + this._pauseOnNextStatement = step; const endTime = monotonicTime(); for (const [metadata, { resolve }] of this._pausedCallsMetadata) { diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 17235a6362..e1a73b145e 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -34,6 +34,9 @@ export class DebugControllerDispatcher extends Dispatcher { this._dispatchEvent('sourceChanged', ({ text, header, footer, actions })); }); + this._object.on(DebugController.Events.Paused, ({ paused }) => { + this._dispatchEvent('paused', ({ paused })); + }); } async initialize(params: channels.DebugControllerInitializeParams) { @@ -64,6 +67,10 @@ export class DebugControllerDispatcher extends Dispatcher Promise; private _omitCallTracking = false; private _currentLanguage: Language; + private static recorderAppFactory: ((recorder: Recorder) => Promise) | undefined; + + static setAppFactory(recorderAppFactory: ((recorder: Recorder) => Promise) | undefined) { + Recorder.recorderAppFactory = recorderAppFactory; + } + static showInspector(context: BrowserContext) { Recorder.show(context, {}).catch(() => {}); } - static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}, recorderAppFactory = Recorder.defaultRecorderAppFactory): Promise { + static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise { let recorderPromise = (context as any)[recorderSymbol] as Promise; if (!recorderPromise) { - const recorder = new Recorder(context, params, recorderAppFactory); + const recorder = new Recorder(context, params); recorderPromise = recorder.install().then(() => recorder); (context as any)[recorderSymbol] = recorderPromise; } return recorderPromise; } - constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams, recorderAppFactory: (recorder: Recorder) => Promise) { + constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) { this._mode = params.mode || 'none'; - this._recorderAppFactory = recorderAppFactory; this._contextRecorder = new ContextRecorder(context, params); this._context = context; this._omitCallTracking = !!params.omitCallTracking; @@ -95,7 +99,7 @@ export class Recorder implements InstrumentationListener { } async install() { - const recorderApp = await this._recorderAppFactory(this); + const recorderApp = await (Recorder.recorderAppFactory || Recorder.defaultRecorderAppFactory)(this); this._recorderApp = recorderApp; recorderApp.once('close', () => { this._debugger.resume(false); @@ -215,6 +219,10 @@ export class Recorder implements InstrumentationListener { this._refreshOverlay(); } + resume() { + this._debugger.resume(false); + } + setHighlightedSelector(language: Language, selector: string) { this._highlightedSelector = locatorOrSelectorAsSelector(language, selector, this._contextRecorder.testIdAttributeName()); this._refreshOverlay(); diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 59412f4d50..f53f58399f 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -592,6 +592,7 @@ export interface DebugControllerEventTarget { on(event: 'inspectRequested', callback: (params: DebugControllerInspectRequestedEvent) => void): this; on(event: 'stateChanged', callback: (params: DebugControllerStateChangedEvent) => void): this; on(event: 'sourceChanged', callback: (params: DebugControllerSourceChangedEvent) => void): this; + on(event: 'paused', callback: (params: DebugControllerPausedEvent) => void): this; on(event: 'browsersChanged', callback: (params: DebugControllerBrowsersChangedEvent) => void): this; } export interface DebugControllerChannel extends DebugControllerEventTarget, Channel { @@ -603,6 +604,7 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan setRecorderMode(params: DebugControllerSetRecorderModeParams, metadata?: Metadata): Promise; highlight(params: DebugControllerHighlightParams, metadata?: Metadata): Promise; hideHighlight(params?: DebugControllerHideHighlightParams, metadata?: Metadata): Promise; + resume(params?: DebugControllerResumeParams, metadata?: Metadata): Promise; kill(params?: DebugControllerKillParams, metadata?: Metadata): Promise; closeAllBrowsers(params?: DebugControllerCloseAllBrowsersParams, metadata?: Metadata): Promise; } @@ -619,6 +621,9 @@ export type DebugControllerSourceChangedEvent = { footer?: string, actions?: string[], }; +export type DebugControllerPausedEvent = { + paused: boolean, +}; export type DebugControllerBrowsersChangedEvent = { browsers: { contexts: { @@ -669,6 +674,9 @@ export type DebugControllerHighlightResult = void; export type DebugControllerHideHighlightParams = {}; export type DebugControllerHideHighlightOptions = {}; export type DebugControllerHideHighlightResult = void; +export type DebugControllerResumeParams = {}; +export type DebugControllerResumeOptions = {}; +export type DebugControllerResumeResult = void; export type DebugControllerKillParams = {}; export type DebugControllerKillOptions = {}; export type DebugControllerKillResult = void; @@ -680,6 +688,7 @@ export interface DebugControllerEvents { 'inspectRequested': DebugControllerInspectRequestedEvent; 'stateChanged': DebugControllerStateChangedEvent; 'sourceChanged': DebugControllerSourceChangedEvent; + 'paused': DebugControllerPausedEvent; 'browsersChanged': DebugControllerBrowsersChangedEvent; } diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 45515d2d98..3fee377243 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -700,6 +700,8 @@ DebugController: hideHighlight: + resume: + kill: closeAllBrowsers: @@ -722,7 +724,10 @@ DebugController: actions: type: array? items: string - + + paused: + parameters: + paused: boolean # Deprecated browsersChanged: diff --git a/tests/config/debugControllerBackend.ts b/tests/config/debugControllerBackend.ts index cffb65eda7..50408aa814 100644 --- a/tests/config/debugControllerBackend.ts +++ b/tests/config/debugControllerBackend.ts @@ -137,6 +137,10 @@ export class Backend extends EventEmitter { }; } + async initialize() { + await this._send('initialize', { codegenId: 'playwright-test', sdkLanguage: 'javascript' }); + } + async close() { await this._transport.closeAndWait(); } @@ -165,6 +169,10 @@ export class Backend extends EventEmitter { await this._send('hideHighlight'); } + async resume() { + this._send('resume'); + } + async kill() { this._send('kill'); } diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index faea27dfd5..dff037b37a 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -37,6 +37,7 @@ const test = baseTest.extend({ backend: async ({ wsEndpoint }, use) => { const backend = new Backend(); await backend.connect(wsEndpoint); + await backend.initialize(); await use(backend); await backend.close(); }, @@ -212,3 +213,16 @@ test('test', async ({ page }) => { });` }); }); + + +test('should pause and resume', async ({ backend, connectedBrowser }) => { + const events = []; + backend.on('paused', event => events.push(event)); + const context = await connectedBrowser._newContextForReuse(); + const page = await context.newPage(); + await page.setContent(''); + const pausePromise = page.pause(); + await expect.poll(() => events[events.length - 1]).toEqual({ paused: true }); + await backend.resume(); + await pausePromise; +});