chore: report paused signal to the debug controller clients (#18701)

This commit is contained in:
Pavel Feldman 2022-11-10 12:15:29 -08:00 committed by GitHub
parent f52fa4ceba
commit ca2e7ef199
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 79 additions and 8 deletions

View file

@ -344,6 +344,9 @@ scheme.DebugControllerSourceChangedEvent = tObject({
footer: tOptional(tString), footer: tOptional(tString),
actions: tOptional(tArray(tString)), actions: tOptional(tArray(tString)),
}); });
scheme.DebugControllerPausedEvent = tObject({
paused: tBoolean,
});
scheme.DebugControllerBrowsersChangedEvent = tObject({ scheme.DebugControllerBrowsersChangedEvent = tObject({
browsers: tArray(tObject({ browsers: tArray(tObject({
contexts: tArray(tObject({ contexts: tArray(tObject({
@ -377,6 +380,8 @@ scheme.DebugControllerHighlightParams = tObject({
scheme.DebugControllerHighlightResult = tOptional(tObject({})); scheme.DebugControllerHighlightResult = tOptional(tObject({}));
scheme.DebugControllerHideHighlightParams = tOptional(tObject({})); scheme.DebugControllerHideHighlightParams = tOptional(tObject({}));
scheme.DebugControllerHideHighlightResult = tOptional(tObject({})); scheme.DebugControllerHideHighlightResult = tOptional(tObject({}));
scheme.DebugControllerResumeParams = tOptional(tObject({}));
scheme.DebugControllerResumeResult = tOptional(tObject({}));
scheme.DebugControllerKillParams = tOptional(tObject({})); scheme.DebugControllerKillParams = tOptional(tObject({}));
scheme.DebugControllerKillResult = tOptional(tObject({})); scheme.DebugControllerKillResult = tOptional(tObject({}));
scheme.DebugControllerCloseAllBrowsersParams = tOptional(tObject({})); scheme.DebugControllerCloseAllBrowsersParams = tOptional(tObject({}));

View file

@ -34,6 +34,7 @@ export class DebugController extends SdkObject {
StateChanged: 'stateChanged', StateChanged: 'stateChanged',
InspectRequested: 'inspectRequested', InspectRequested: 'inspectRequested',
SourceChanged: 'sourceChanged', SourceChanged: 'sourceChanged',
Paused: 'paused',
}; };
private _autoCloseTimer: NodeJS.Timeout | undefined; private _autoCloseTimer: NodeJS.Timeout | undefined;
@ -52,6 +53,7 @@ export class DebugController extends SdkObject {
initialize(codegenId: string, sdkLanguage: Language) { initialize(codegenId: string, sdkLanguage: Language) {
this._codegenId = codegenId; this._codegenId = codegenId;
this._sdkLanguage = sdkLanguage; this._sdkLanguage = sdkLanguage;
Recorder.setAppFactory(async () => new InspectingRecorderApp(this));
} }
setAutoCloseAllowed(allowed: boolean) { setAutoCloseAllowed(allowed: boolean) {
@ -61,6 +63,7 @@ export class DebugController extends SdkObject {
dispose() { dispose() {
this.setReportStateChanged(false); this.setReportStateChanged(false);
this.setAutoCloseAllowed(false); this.setAutoCloseAllowed(false);
Recorder.setAppFactory(undefined);
} }
setReportStateChanged(enabled: boolean) { setReportStateChanged(enabled: boolean) {
@ -157,6 +160,11 @@ export class DebugController extends SdkObject {
return [...this._playwright.allBrowsers()]; return [...this._playwright.allBrowsers()];
} }
async resume() {
for (const recorder of await this._allRecorders())
recorder.resume();
}
async kill() { async kill() {
selfDestruct(); selfDestruct();
} }
@ -192,7 +200,7 @@ export class DebugController extends SdkObject {
const contexts = new Set<BrowserContext>(); const contexts = new Set<BrowserContext>();
for (const page of this._playwright.allPages()) for (const page of this._playwright.allPages())
contexts.add(page.context()); 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[]; return result.filter(Boolean) as Recorder[];
} }
@ -235,4 +243,8 @@ class InspectingRecorderApp extends EmptyRecorderApp {
const { text, header, footer, actions } = source || { text: '' }; const { text, header, footer, actions } = source || { text: '' };
this._debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions }); this._debugController.emit(DebugController.Events.SourceChanged, { text, header, footer, actions });
} }
override async setPaused(paused: boolean) {
this._debugController.emit(DebugController.Events.Paused, { paused });
}
} }

View file

@ -83,6 +83,9 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
} }
resume(step: boolean) { resume(step: boolean) {
if (!this.isPaused())
return;
this._pauseOnNextStatement = step; this._pauseOnNextStatement = step;
const endTime = monotonicTime(); const endTime = monotonicTime();
for (const [metadata, { resolve }] of this._pausedCallsMetadata) { for (const [metadata, { resolve }] of this._pausedCallsMetadata) {

View file

@ -34,6 +34,9 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
this._object.on(DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => { this._object.on(DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => {
this._dispatchEvent('sourceChanged', ({ text, header, footer, actions })); this._dispatchEvent('sourceChanged', ({ text, header, footer, actions }));
}); });
this._object.on(DebugController.Events.Paused, ({ paused }) => {
this._dispatchEvent('paused', ({ paused }));
});
} }
async initialize(params: channels.DebugControllerInitializeParams) { async initialize(params: channels.DebugControllerInitializeParams) {
@ -64,6 +67,10 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
await this._object.hideHighlight(); await this._object.hideHighlight();
} }
async resume() {
await this._object.resume();
}
async kill() { async kill() {
await this._object.kill(); await this._object.kill();
} }

View file

@ -58,27 +58,31 @@ export class Recorder implements InstrumentationListener {
private _debugger: Debugger; private _debugger: Debugger;
private _contextRecorder: ContextRecorder; private _contextRecorder: ContextRecorder;
private _handleSIGINT: boolean | undefined; private _handleSIGINT: boolean | undefined;
private _recorderAppFactory: (recorder: Recorder) => Promise<IRecorderApp>;
private _omitCallTracking = false; private _omitCallTracking = false;
private _currentLanguage: Language; private _currentLanguage: Language;
private static recorderAppFactory: ((recorder: Recorder) => Promise<IRecorderApp>) | undefined;
static setAppFactory(recorderAppFactory: ((recorder: Recorder) => Promise<IRecorderApp>) | undefined) {
Recorder.recorderAppFactory = recorderAppFactory;
}
static showInspector(context: BrowserContext) { static showInspector(context: BrowserContext) {
Recorder.show(context, {}).catch(() => {}); Recorder.show(context, {}).catch(() => {});
} }
static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}, recorderAppFactory = Recorder.defaultRecorderAppFactory): Promise<Recorder> { static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise<Recorder> {
let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>; let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>;
if (!recorderPromise) { if (!recorderPromise) {
const recorder = new Recorder(context, params, recorderAppFactory); const recorder = new Recorder(context, params);
recorderPromise = recorder.install().then(() => recorder); recorderPromise = recorder.install().then(() => recorder);
(context as any)[recorderSymbol] = recorderPromise; (context as any)[recorderSymbol] = recorderPromise;
} }
return recorderPromise; return recorderPromise;
} }
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams, recorderAppFactory: (recorder: Recorder) => Promise<IRecorderApp>) { constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
this._mode = params.mode || 'none'; this._mode = params.mode || 'none';
this._recorderAppFactory = recorderAppFactory;
this._contextRecorder = new ContextRecorder(context, params); this._contextRecorder = new ContextRecorder(context, params);
this._context = context; this._context = context;
this._omitCallTracking = !!params.omitCallTracking; this._omitCallTracking = !!params.omitCallTracking;
@ -95,7 +99,7 @@ export class Recorder implements InstrumentationListener {
} }
async install() { async install() {
const recorderApp = await this._recorderAppFactory(this); const recorderApp = await (Recorder.recorderAppFactory || Recorder.defaultRecorderAppFactory)(this);
this._recorderApp = recorderApp; this._recorderApp = recorderApp;
recorderApp.once('close', () => { recorderApp.once('close', () => {
this._debugger.resume(false); this._debugger.resume(false);
@ -215,6 +219,10 @@ export class Recorder implements InstrumentationListener {
this._refreshOverlay(); this._refreshOverlay();
} }
resume() {
this._debugger.resume(false);
}
setHighlightedSelector(language: Language, selector: string) { setHighlightedSelector(language: Language, selector: string) {
this._highlightedSelector = locatorOrSelectorAsSelector(language, selector, this._contextRecorder.testIdAttributeName()); this._highlightedSelector = locatorOrSelectorAsSelector(language, selector, this._contextRecorder.testIdAttributeName());
this._refreshOverlay(); this._refreshOverlay();

View file

@ -592,6 +592,7 @@ export interface DebugControllerEventTarget {
on(event: 'inspectRequested', callback: (params: DebugControllerInspectRequestedEvent) => void): this; on(event: 'inspectRequested', callback: (params: DebugControllerInspectRequestedEvent) => void): this;
on(event: 'stateChanged', callback: (params: DebugControllerStateChangedEvent) => void): this; on(event: 'stateChanged', callback: (params: DebugControllerStateChangedEvent) => void): this;
on(event: 'sourceChanged', callback: (params: DebugControllerSourceChangedEvent) => 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; on(event: 'browsersChanged', callback: (params: DebugControllerBrowsersChangedEvent) => void): this;
} }
export interface DebugControllerChannel extends DebugControllerEventTarget, Channel { export interface DebugControllerChannel extends DebugControllerEventTarget, Channel {
@ -603,6 +604,7 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan
setRecorderMode(params: DebugControllerSetRecorderModeParams, metadata?: Metadata): Promise<DebugControllerSetRecorderModeResult>; setRecorderMode(params: DebugControllerSetRecorderModeParams, metadata?: Metadata): Promise<DebugControllerSetRecorderModeResult>;
highlight(params: DebugControllerHighlightParams, metadata?: Metadata): Promise<DebugControllerHighlightResult>; highlight(params: DebugControllerHighlightParams, metadata?: Metadata): Promise<DebugControllerHighlightResult>;
hideHighlight(params?: DebugControllerHideHighlightParams, metadata?: Metadata): Promise<DebugControllerHideHighlightResult>; hideHighlight(params?: DebugControllerHideHighlightParams, metadata?: Metadata): Promise<DebugControllerHideHighlightResult>;
resume(params?: DebugControllerResumeParams, metadata?: Metadata): Promise<DebugControllerResumeResult>;
kill(params?: DebugControllerKillParams, metadata?: Metadata): Promise<DebugControllerKillResult>; kill(params?: DebugControllerKillParams, metadata?: Metadata): Promise<DebugControllerKillResult>;
closeAllBrowsers(params?: DebugControllerCloseAllBrowsersParams, metadata?: Metadata): Promise<DebugControllerCloseAllBrowsersResult>; closeAllBrowsers(params?: DebugControllerCloseAllBrowsersParams, metadata?: Metadata): Promise<DebugControllerCloseAllBrowsersResult>;
} }
@ -619,6 +621,9 @@ export type DebugControllerSourceChangedEvent = {
footer?: string, footer?: string,
actions?: string[], actions?: string[],
}; };
export type DebugControllerPausedEvent = {
paused: boolean,
};
export type DebugControllerBrowsersChangedEvent = { export type DebugControllerBrowsersChangedEvent = {
browsers: { browsers: {
contexts: { contexts: {
@ -669,6 +674,9 @@ export type DebugControllerHighlightResult = void;
export type DebugControllerHideHighlightParams = {}; export type DebugControllerHideHighlightParams = {};
export type DebugControllerHideHighlightOptions = {}; export type DebugControllerHideHighlightOptions = {};
export type DebugControllerHideHighlightResult = void; export type DebugControllerHideHighlightResult = void;
export type DebugControllerResumeParams = {};
export type DebugControllerResumeOptions = {};
export type DebugControllerResumeResult = void;
export type DebugControllerKillParams = {}; export type DebugControllerKillParams = {};
export type DebugControllerKillOptions = {}; export type DebugControllerKillOptions = {};
export type DebugControllerKillResult = void; export type DebugControllerKillResult = void;
@ -680,6 +688,7 @@ export interface DebugControllerEvents {
'inspectRequested': DebugControllerInspectRequestedEvent; 'inspectRequested': DebugControllerInspectRequestedEvent;
'stateChanged': DebugControllerStateChangedEvent; 'stateChanged': DebugControllerStateChangedEvent;
'sourceChanged': DebugControllerSourceChangedEvent; 'sourceChanged': DebugControllerSourceChangedEvent;
'paused': DebugControllerPausedEvent;
'browsersChanged': DebugControllerBrowsersChangedEvent; 'browsersChanged': DebugControllerBrowsersChangedEvent;
} }

View file

@ -700,6 +700,8 @@ DebugController:
hideHighlight: hideHighlight:
resume:
kill: kill:
closeAllBrowsers: closeAllBrowsers:
@ -722,7 +724,10 @@ DebugController:
actions: actions:
type: array? type: array?
items: string items: string
paused:
parameters:
paused: boolean
# Deprecated # Deprecated
browsersChanged: browsersChanged:

View file

@ -137,6 +137,10 @@ export class Backend extends EventEmitter {
}; };
} }
async initialize() {
await this._send('initialize', { codegenId: 'playwright-test', sdkLanguage: 'javascript' });
}
async close() { async close() {
await this._transport.closeAndWait(); await this._transport.closeAndWait();
} }
@ -165,6 +169,10 @@ export class Backend extends EventEmitter {
await this._send('hideHighlight'); await this._send('hideHighlight');
} }
async resume() {
this._send('resume');
}
async kill() { async kill() {
this._send('kill'); this._send('kill');
} }

View file

@ -37,6 +37,7 @@ const test = baseTest.extend<Fixtures>({
backend: async ({ wsEndpoint }, use) => { backend: async ({ wsEndpoint }, use) => {
const backend = new Backend(); const backend = new Backend();
await backend.connect(wsEndpoint); await backend.connect(wsEndpoint);
await backend.initialize();
await use(backend); await use(backend);
await backend.close(); 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('<button>Submit</button>');
const pausePromise = page.pause();
await expect.poll(() => events[events.length - 1]).toEqual({ paused: true });
await backend.resume();
await pausePromise;
});