Skip to content

Commit

Permalink
feat(inspector): render api names from metainfo (#5530)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Feb 20, 2021
1 parent d6ac3e6 commit 600f731
Show file tree
Hide file tree
Showing 15 changed files with 54 additions and 48 deletions.
2 changes: 1 addition & 1 deletion src/client/android.ts
Expand Up @@ -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'));
Expand Down
8 changes: 1 addition & 7 deletions src/client/browserContext.ts
Expand Up @@ -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'));
Expand Down Expand Up @@ -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,
Expand Down
43 changes: 24 additions & 19 deletions src/client/channelOwner.ts
Expand Up @@ -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;
}

Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions src/client/connection.ts
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/client/electron.ts
Expand Up @@ -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'));
Expand Down
4 changes: 2 additions & 2 deletions src/client/frame.ts
Expand Up @@ -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}"`);
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/client/network.ts
Expand Up @@ -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'));
Expand Down
6 changes: 4 additions & 2 deletions src/client/page.ts
Expand Up @@ -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}"`);
Expand Down Expand Up @@ -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> {
Expand Down
4 changes: 2 additions & 2 deletions src/client/waiter.ts
Expand Up @@ -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> {
Expand Down
1 change: 1 addition & 0 deletions src/protocol/channels.ts
Expand Up @@ -32,6 +32,7 @@ export type StackFrame = {

export type Metadata = {
stack?: StackFrame[],
apiName?: string,
};

export type WaitForEventInfo = {
Expand Down
1 change: 1 addition & 0 deletions src/protocol/protocol.yml
Expand Up @@ -28,6 +28,7 @@ Metadata:
stack:
type: array?
items: StackFrame
apiName: string?


WaitForEventInfo:
Expand Down
1 change: 1 addition & 0 deletions src/protocol/validator.ts
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/server/instrumentation.ts
Expand Up @@ -39,6 +39,7 @@ export type CallMetadata = {
type: string;
method: string;
params: any;
apiName?: string;
stack?: StackFrame[];
log: string[];
error?: string;
Expand Down
5 changes: 3 additions & 2 deletions src/server/supplements/recorderSupplement.ts
Expand Up @@ -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';
Expand All @@ -452,7 +452,8 @@ export class RecorderSupplement {
logs.push({
id: metadata.id,
messages: metadata.log,
title, status,
title,
status,
error: metadata.error,
params,
duration
Expand Down
18 changes: 9 additions & 9 deletions test/pause.spec.ts
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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',
Expand Down

0 comments on commit 600f731

Please sign in to comment.