feat(inspector): render api names from metainfo (#5530)

This commit is contained in:
Pavel Feldman 2021-02-19 18:12:33 -08:00 committed by GitHub
parent d6ac3e6883
commit 600f731a67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 54 additions and 48 deletions

View File

@ -239,7 +239,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event);
const waiter = Waiter.createForEvent(this, 'androidDevice', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.AndroidDevice.Close)
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));

View File

@ -217,7 +217,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event);
const waiter = Waiter.createForEvent(this, 'browserContext', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.BrowserContext.Close)
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
@ -256,12 +256,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
}
async _pause() {
return this._wrapApiCall('browserContext.pause', async (channel: channels.BrowserContextChannel) => {
await channel.pause();
});
}
async _enableRecorder(params: {
language: string,
launchOptions?: LaunchOptions,

View File

@ -48,20 +48,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
this._logger = this._parent._logger;
}
const base = new EventEmitter();
this._channel = new Proxy(base, {
get: (obj: any, prop) => {
if (prop === 'debugScopeState')
return (params: any) => this._connection.sendMessageToServer(guid, prop, params);
if (typeof prop === 'string') {
const validator = scheme[paramsName(type, prop)];
if (validator)
return (params: any) => this._connection.sendMessageToServer(guid, prop, validator(params, ''));
}
return obj[prop];
},
});
(this._channel as any)._object = this;
this._channel = this._createChannel(new EventEmitter(), '');
this._initializer = initializer;
}
@ -84,11 +71,29 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
};
}
async _wrapApiCall<T, K extends channels.Channel>(apiName: string, func: (channel: K) => Promise<T>, logger?: Logger): Promise<T> {
_createChannel(base: Object, apiName: string): T {
const channel = new Proxy(base, {
get: (obj: any, prop) => {
if (prop === 'debugScopeState')
return (params: any) => this._connection.sendMessageToServer(this._guid, prop, params, apiName);
if (typeof prop === 'string') {
const validator = scheme[paramsName(this._type, prop)];
if (validator)
return (params: any) => this._connection.sendMessageToServer(this._guid, prop, validator(params, ''), apiName);
}
return obj[prop];
},
});
(channel as any)._object = this;
return channel;
}
async _wrapApiCall<R, C extends channels.Channel>(apiName: string, func: (channel: C) => Promise<R>, logger?: Logger): Promise<R> {
logger = logger || this._logger;
try {
logApiCall(logger, `=> ${apiName} started`);
const result = await func(this._channel as any);
const channel = this._createChannel({}, apiName);
const result = await func(channel as any);
logApiCall(logger, `<= ${apiName} succeeded`);
return result;
} catch (e) {
@ -99,15 +104,15 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
}
_waitForEventInfoBefore(waitId: string, name: string, stack: StackFrame[]) {
this._connection.sendMessageToServer(this._guid, 'waitForEventInfo', { info: { name, waitId, phase: 'before', stack } }).catch(() => {});
this._connection.sendMessageToServer(this._guid, 'waitForEventInfo', { info: { name, waitId, phase: 'before', stack } }, undefined).catch(() => {});
}
_waitForEventInfoAfter(waitId: string, error?: string) {
this._connection.sendMessageToServer(this._guid, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }).catch(() => {});
this._connection.sendMessageToServer(this._guid, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }, undefined).catch(() => {});
}
_waitForEventInfoLog(waitId: string, message: string) {
this._connection.sendMessageToServer(this._guid, 'waitForEventInfo', { info: { waitId, phase: 'log', message } }).catch(() => {});
this._connection.sendMessageToServer(this._guid, 'waitForEventInfo', { info: { waitId, phase: 'log', message } }, undefined).catch(() => {});
}
private toJSON() {

View File

@ -71,13 +71,13 @@ export class Connection {
return this._objects.get(guid)!;
}
async sendMessageToServer(guid: string, method: string, params: any): Promise<any> {
async sendMessageToServer(guid: string, method: string, params: any, apiName: string | undefined): Promise<any> {
const { stack, frames } = captureStackTrace();
const id = ++this._lastId;
const converted = { id, guid, method, params };
// Do not include metadata in debug logs to avoid noise.
debugLogger.log('channel:command', converted);
this.onmessage({ ...converted, metadata: { stack: frames } });
this.onmessage({ ...converted, metadata: { stack: frames, apiName } });
try {
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject }));
} catch (e) {

View File

@ -101,7 +101,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event);
const waiter = Waiter.createForEvent(this, 'electronApplication', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.ElectronApplication.Close)
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));

View File

@ -113,7 +113,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
async waitForNavigation(options: WaitForNavigationOptions = {}): Promise<network.Response | null> {
return this._wrapApiCall(this._apiName('waitForNavigation'), async (channel: channels.FrameChannel) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
const waiter = this._setupNavigationWaiter('waitForNavigation', options);
const waiter = this._setupNavigationWaiter(this._apiName('waitForNavigation'), options);
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
@ -150,7 +150,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
if (this._loadStates.has(state))
return;
return this._wrapApiCall(this._apiName('waitForLoadState'), async (channel: channels.FrameChannel) => {
const waiter = this._setupNavigationWaiter('waitForLoadState', options);
const waiter = this._setupNavigationWaiter(this._apiName('waitForLoadState'), options);
await waiter.waitForEvent<LifecycleEvent>(this._eventEmitter, 'loadstate', s => {
waiter.log(` "${s}" event fired`);
return s === state;

View File

@ -365,7 +365,7 @@ export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event);
const waiter = Waiter.createForEvent(this, 'webSocket', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.WebSocket.Error)
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));

View File

@ -397,7 +397,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event);
const waiter = Waiter.createForEvent(this, 'page', event);
if (logLine)
waiter.log(logLine);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
@ -644,7 +644,9 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}
async pause() {
await this.context()._pause();
return this.context()._wrapApiCall('page.pause', async (channel: channels.BrowserContextChannel) => {
await channel.pause();
});
}
async _pdf(options: PDFOptions = {}): Promise<Buffer> {

View File

@ -38,8 +38,8 @@ export class Waiter {
];
}
static createForEvent(channelOwner: ChannelOwner, event: string) {
return new Waiter(channelOwner, `waitForEvent(${event})`);
static createForEvent(channelOwner: ChannelOwner, target: string, event: string) {
return new Waiter(channelOwner, `${target}.waitForEvent(${event})`);
}
async waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean): Promise<T> {

View File

@ -32,6 +32,7 @@ export type StackFrame = {
export type Metadata = {
stack?: StackFrame[],
apiName?: string,
};
export type WaitForEventInfo = {

View File

@ -28,6 +28,7 @@ Metadata:
stack:
type: array?
items: StackFrame
apiName: string?
WaitForEventInfo:

View File

@ -41,6 +41,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
});
scheme.Metadata = tObject({
stack: tOptional(tArray(tType('StackFrame'))),
apiName: tOptional(tString),
});
scheme.WaitForEventInfo = tObject({
waitId: tString,

View File

@ -39,6 +39,7 @@ export type CallMetadata = {
type: string;
method: string;
params: any;
apiName?: string;
stack?: StackFrame[];
log: string[];
error?: string;

View File

@ -434,7 +434,7 @@ export class RecorderSupplement {
for (const metadata of metadatas) {
if (!metadata.method)
continue;
const title = metadata.method;
const title = metadata.apiName || metadata.method;
let status: 'done' | 'in-progress' | 'paused' | 'error' = 'done';
if (this._currentCallsMetadata.has(metadata))
status = 'in-progress';
@ -452,7 +452,8 @@ export class RecorderSupplement {
logs.push({
id: metadata.id,
messages: metadata.log,
title, status,
title,
status,
error: metadata.error,
params,
duration

View File

@ -155,9 +155,9 @@ describe('pause', (suite, { mode }) => {
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
expect(await sanitizeLog(recorderPage)).toEqual([
'pause- XXms',
'click(button)- XXms',
'pause',
'page.pause- XXms',
'page.click(button)- XXms',
'page.pause',
]);
await recorderPage.click('[title="Resume"]');
await scriptPromise;
@ -177,10 +177,10 @@ describe('pause', (suite, { mode }) => {
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
expect(await sanitizeLog(recorderPage)).toEqual([
'pause- XXms',
'waitForEvent(console)- XXms',
'click(button)- XXms',
'pause',
'page.pause- XXms',
'page.waitForEvent(console)- XXms',
'page.click(button)- XXms',
'page.pause',
]);
await recorderPage.click('[title="Resume"]');
await scriptPromise;
@ -196,8 +196,8 @@ describe('pause', (suite, { mode }) => {
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-error');
expect(await sanitizeLog(recorderPage)).toEqual([
'pause- XXms',
'isChecked(button)- XXms',
'page.pause- XXms',
'page.isChecked(button)- XXms',
'checking \"checked\" state of \"button\"',
'selector resolved to <button onclick=\"console.log(1)\">Submit</button>',
'Not a checkbox or radio button',