mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(trace-viewer): include waitFor* in trace viewer (#7413)
This commit is contained in:
parent
63e6e530ca
commit
f43b4efbc9
@ -26,7 +26,6 @@ import { Page } from './page';
|
|||||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { ParsedStackTrace } from '../utils/stackTrace';
|
|
||||||
|
|
||||||
type Direction = 'down' | 'up' | 'left' | 'right';
|
type Direction = 'down' | 'up' | 'left' | 'right';
|
||||||
type SpeedOptions = { speed?: number };
|
type SpeedOptions = { speed?: number };
|
||||||
@ -236,10 +235,10 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
|
||||||
return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
|
||||||
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
const waiter = Waiter.createForEvent(this, event, stackTrace);
|
const waiter = Waiter.createForEvent(this, event);
|
||||||
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
||||||
if (event !== Events.AndroidDevice.Close)
|
if (event !== Events.AndroidDevice.Close)
|
||||||
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));
|
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));
|
||||||
|
|||||||
@ -33,7 +33,6 @@ import * as api from '../../types/types';
|
|||||||
import * as structs from '../../types/structs';
|
import * as structs from '../../types/structs';
|
||||||
import { CDPSession } from './cdpSession';
|
import { CDPSession } from './cdpSession';
|
||||||
import { Tracing } from './tracing';
|
import { Tracing } from './tracing';
|
||||||
import { ParsedStackTrace } from '../utils/stackTrace';
|
|
||||||
|
|
||||||
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext {
|
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext {
|
||||||
_pages = new Set<Page>();
|
_pages = new Set<Page>();
|
||||||
@ -270,10 +269,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
||||||
return this._wrapApiCall(async (channel: channels.BrowserContextChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
||||||
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
const waiter = Waiter.createForEvent(this, event, stackTrace);
|
const waiter = Waiter.createForEvent(this, event);
|
||||||
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
||||||
if (event !== Events.BrowserContext.Close)
|
if (event !== Events.BrowserContext.Close)
|
||||||
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
|
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
|
||||||
|
|||||||
@ -107,18 +107,6 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitForEventInfoBefore(waitId: string, event: string, stackTrace: ParsedStackTrace) {
|
|
||||||
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'before', event } }, stackTrace).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
_waitForEventInfoAfter(waitId: string, error: string | undefined) {
|
|
||||||
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }, null).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
_waitForEventInfoLog(waitId: string, message: string) {
|
|
||||||
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'log', message } }, null).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private toJSON() {
|
private toJSON() {
|
||||||
// Jest's expect library tries to print objects sometimes.
|
// Jest's expect library tries to print objects sometimes.
|
||||||
// RPC objects can contain links to lots of other objects,
|
// RPC objects can contain links to lots of other objects,
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import type { BrowserWindow } from 'electron';
|
|||||||
import * as structs from '../../types/structs';
|
import * as structs from '../../types/structs';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { ParsedStackTrace } from '../utils/stackTrace';
|
|
||||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||||
import { headersObjectToArray } from '../utils/utils';
|
import { headersObjectToArray } from '../utils/utils';
|
||||||
import { BrowserContext } from './browserContext';
|
import { BrowserContext } from './browserContext';
|
||||||
@ -101,16 +100,16 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
|||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
|
||||||
await channel.close();
|
await channel.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
||||||
return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
|
||||||
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
const waiter = Waiter.createForEvent(this, event, stackTrace);
|
const waiter = Waiter.createForEvent(this, event);
|
||||||
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
||||||
if (event !== Events.ElectronApplication.Close)
|
if (event !== Events.ElectronApplication.Close)
|
||||||
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));
|
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));
|
||||||
|
|||||||
@ -30,7 +30,6 @@ import { LifecycleEvent, URLMatch, SelectOption, SelectOptionOptions, FilePayloa
|
|||||||
import { urlMatches } from './clientHelper';
|
import { urlMatches } from './clientHelper';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import * as structs from '../../types/structs';
|
import * as structs from '../../types/structs';
|
||||||
import { ParsedStackTrace } from '../utils/stackTrace';
|
|
||||||
|
|
||||||
export type WaitForNavigationOptions = {
|
export type WaitForNavigationOptions = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
@ -94,8 +93,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setupNavigationWaiter(options: { timeout?: number }, stackTrace: ParsedStackTrace): Waiter {
|
private _setupNavigationWaiter(options: { timeout?: number }): Waiter {
|
||||||
const waiter = new Waiter(this, '', stackTrace);
|
const waiter = new Waiter(this._page!, '');
|
||||||
if (this._page!.isClosed())
|
if (this._page!.isClosed())
|
||||||
waiter.rejectImmediately(new Error('Navigation failed because page was closed!'));
|
waiter.rejectImmediately(new Error('Navigation failed because page was closed!'));
|
||||||
waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!'));
|
waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!'));
|
||||||
@ -107,9 +106,9 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForNavigation(options: WaitForNavigationOptions = {}): Promise<network.Response | null> {
|
async waitForNavigation(options: WaitForNavigationOptions = {}): Promise<network.Response | null> {
|
||||||
return this._wrapApiCall(async (channel: channels.FrameChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.FrameChannel) => {
|
||||||
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
const waiter = this._setupNavigationWaiter(options, stackTrace);
|
const waiter = this._setupNavigationWaiter(options);
|
||||||
|
|
||||||
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
|
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
|
||||||
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
|
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
|
||||||
@ -145,8 +144,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
|
|||||||
state = verifyLoadState('state', state);
|
state = verifyLoadState('state', state);
|
||||||
if (this._loadStates.has(state))
|
if (this._loadStates.has(state))
|
||||||
return;
|
return;
|
||||||
return this._wrapApiCall(async (channel: channels.FrameChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.FrameChannel) => {
|
||||||
const waiter = this._setupNavigationWaiter(options, stackTrace);
|
const waiter = this._setupNavigationWaiter(options);
|
||||||
await waiter.waitForEvent<LifecycleEvent>(this._eventEmitter, 'loadstate', s => {
|
await waiter.waitForEvent<LifecycleEvent>(this._eventEmitter, 'loadstate', s => {
|
||||||
waiter.log(` "${s}" event fired`);
|
waiter.log(` "${s}" event fired`);
|
||||||
return s === state;
|
return s === state;
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import { Events } from './events';
|
|||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import { ParsedStackTrace } from '../utils/stackTrace';
|
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
name: string,
|
name: string,
|
||||||
@ -476,10 +475,10 @@ export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
||||||
return this._wrapApiCall(async (channel: channels.WebSocketChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.WebSocketChannel) => {
|
||||||
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
const waiter = Waiter.createForEvent(this, event, stackTrace);
|
const waiter = Waiter.createForEvent(this, event);
|
||||||
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
||||||
if (event !== Events.WebSocket.Error)
|
if (event !== Events.WebSocket.Error)
|
||||||
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));
|
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));
|
||||||
|
|||||||
@ -46,7 +46,6 @@ import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } fro
|
|||||||
import { isSafeCloseError } from '../utils/errors';
|
import { isSafeCloseError } from '../utils/errors';
|
||||||
import { Video } from './video';
|
import { Video } from './video';
|
||||||
import { Artifact } from './artifact';
|
import { Artifact } from './artifact';
|
||||||
import { ParsedStackTrace } from '../utils/stackTrace';
|
|
||||||
|
|
||||||
type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
|
type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
|
||||||
width?: string | number,
|
width?: string | number,
|
||||||
@ -349,7 +348,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Request> {
|
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Request> {
|
||||||
return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.PageChannel) => {
|
||||||
const predicate = (request: Request) => {
|
const predicate = (request: Request) => {
|
||||||
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
|
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
|
||||||
return urlMatches(request.url(), urlOrPredicate);
|
return urlMatches(request.url(), urlOrPredicate);
|
||||||
@ -357,12 +356,12 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
|||||||
};
|
};
|
||||||
const trimmedUrl = trimUrl(urlOrPredicate);
|
const trimmedUrl = trimUrl(urlOrPredicate);
|
||||||
const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined;
|
const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined;
|
||||||
return this._waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout }, stackTrace, logLine);
|
return this._waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout }, logLine);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Response> {
|
async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Response> {
|
||||||
return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.PageChannel) => {
|
||||||
const predicate = (response: Response) => {
|
const predicate = (response: Response) => {
|
||||||
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
|
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
|
||||||
return urlMatches(response.url(), urlOrPredicate);
|
return urlMatches(response.url(), urlOrPredicate);
|
||||||
@ -370,20 +369,20 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
|
|||||||
};
|
};
|
||||||
const trimmedUrl = trimUrl(urlOrPredicate);
|
const trimmedUrl = trimUrl(urlOrPredicate);
|
||||||
const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined;
|
const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined;
|
||||||
return this._waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }, stackTrace, logLine);
|
return this._waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }, logLine);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
||||||
return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
|
return this._wrapApiCall(async (channel: channels.PageChannel) => {
|
||||||
return this._waitForEvent(event, optionsOrPredicate, stackTrace, `waiting for event "${event}"`);
|
return this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, stackTrace: ParsedStackTrace, logLine?: string): Promise<any> {
|
private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise<any> {
|
||||||
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
|
||||||
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
|
||||||
const waiter = Waiter.createForEvent(this, event, stackTrace);
|
const waiter = Waiter.createForEvent(this, event);
|
||||||
if (logLine)
|
if (logLine)
|
||||||
waiter.log(logLine);
|
waiter.log(logLine);
|
||||||
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
|
||||||
|
|||||||
@ -15,9 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { ParsedStackTrace, rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
import { TimeoutError } from '../utils/errors';
|
import { TimeoutError } from '../utils/errors';
|
||||||
import { createGuid } from '../utils/utils';
|
import { createGuid } from '../utils/utils';
|
||||||
|
import * as channels from '../protocol/channels';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
|
||||||
export class Waiter {
|
export class Waiter {
|
||||||
@ -26,21 +27,23 @@ export class Waiter {
|
|||||||
private _immediateError?: Error;
|
private _immediateError?: Error;
|
||||||
// TODO: can/should we move these logs into wrapApiCall?
|
// TODO: can/should we move these logs into wrapApiCall?
|
||||||
private _logs: string[] = [];
|
private _logs: string[] = [];
|
||||||
private _channelOwner: ChannelOwner;
|
private _channel: channels.EventTargetChannel;
|
||||||
private _waitId: string;
|
private _waitId: string;
|
||||||
private _error: string | undefined;
|
private _error: string | undefined;
|
||||||
|
|
||||||
constructor(channelOwner: ChannelOwner, event: string, stackTrace: ParsedStackTrace) {
|
constructor(channelOwner: ChannelOwner<channels.EventTargetChannel>, event: string) {
|
||||||
this._waitId = createGuid();
|
this._waitId = createGuid();
|
||||||
this._channelOwner = channelOwner;
|
this._channel = channelOwner._channel;
|
||||||
this._channelOwner._waitForEventInfoBefore(this._waitId, event, stackTrace);
|
channelOwner._wrapApiCall(async (channel: channels.EventTargetChannel) => {
|
||||||
|
channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'before', event } }).catch(() => {});
|
||||||
|
});
|
||||||
this._dispose = [
|
this._dispose = [
|
||||||
() => this._channelOwner._waitForEventInfoAfter(this._waitId, this._error)
|
() => this._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'after', error: this._error } }).catch(() => {})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static createForEvent(channelOwner: ChannelOwner, event: string, stackTrace: ParsedStackTrace) {
|
static createForEvent(channelOwner: ChannelOwner<channels.EventTargetChannel>, event: string) {
|
||||||
return new Waiter(channelOwner, event, stackTrace);
|
return new Waiter(channelOwner, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean | Promise<boolean>): Promise<T> {
|
async waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean | Promise<boolean>): Promise<T> {
|
||||||
@ -89,7 +92,7 @@ export class Waiter {
|
|||||||
|
|
||||||
log(s: string) {
|
log(s: string) {
|
||||||
this._logs.push(s);
|
this._logs.push(s);
|
||||||
this._channelOwner._waitForEventInfoLog(this._waitId, s);
|
this._channel.waitForEventInfo({ info: { waitId: this._waitId, phase: 'log', message: s } }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _rejectOn(promise: Promise<any>, dispose?: () => void) {
|
private _rejectOn(promise: Promise<any>, dispose?: () => void) {
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export class Dispatcher<Type extends { guid: string }, Initializer> extends Even
|
|||||||
|
|
||||||
(object as any)[dispatcherSymbol] = this;
|
(object as any)[dispatcherSymbol] = this;
|
||||||
if (this._parent)
|
if (this._parent)
|
||||||
this._connection.sendMessageToClient(this._parent._guid, type, '__create__', { type, initializer, guid });
|
this._connection.sendMessageToClient(this._parent._guid, type, '__create__', { type, initializer, guid }, this._parent._object);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dispatchEvent(method: string, params: Dispatcher<any, any> | any = {}) {
|
_dispatchEvent(method: string, params: Dispatcher<any, any> | any = {}) {
|
||||||
@ -142,8 +142,8 @@ export class DispatcherConnection {
|
|||||||
const eventMetadata: CallMetadata = {
|
const eventMetadata: CallMetadata = {
|
||||||
id: `event@${++lastEventId}`,
|
id: `event@${++lastEventId}`,
|
||||||
objectId: sdkObject?.guid,
|
objectId: sdkObject?.guid,
|
||||||
pageId: sdkObject?.attribution.page?.guid,
|
pageId: sdkObject?.attribution?.page?.guid,
|
||||||
frameId: sdkObject?.attribution.frame?.guid,
|
frameId: sdkObject?.attribution?.frame?.guid,
|
||||||
startTime: monotonicTime(),
|
startTime: monotonicTime(),
|
||||||
endTime: 0,
|
endTime: 0,
|
||||||
type,
|
type,
|
||||||
@ -152,7 +152,7 @@ export class DispatcherConnection {
|
|||||||
log: [],
|
log: [],
|
||||||
snapshots: []
|
snapshots: []
|
||||||
};
|
};
|
||||||
sdkObject.instrumentation.onEvent(sdkObject, eventMetadata);
|
sdkObject.instrumentation?.onEvent(sdkObject, eventMetadata);
|
||||||
}
|
}
|
||||||
this.onmessage({ guid, method, params });
|
this.onmessage({ guid, method, params });
|
||||||
}
|
}
|
||||||
@ -176,8 +176,6 @@ export class DispatcherConnection {
|
|||||||
};
|
};
|
||||||
const scheme = createScheme(tChannel);
|
const scheme = createScheme(tChannel);
|
||||||
this._validateParams = (type: string, method: string, params: any): any => {
|
this._validateParams = (type: string, method: string, params: any): any => {
|
||||||
if (method === 'waitForEventInfo')
|
|
||||||
return tOptional(scheme['WaitForEventInfo'])(params.info, '');
|
|
||||||
const name = type + method[0].toUpperCase() + method.substring(1) + 'Params';
|
const name = type + method[0].toUpperCase() + method.substring(1) + 'Params';
|
||||||
if (!scheme[name])
|
if (!scheme[name])
|
||||||
throw new ValidationError(`Unknown scheme for ${type}.${method}`);
|
throw new ValidationError(`Unknown scheme for ${type}.${method}`);
|
||||||
@ -221,8 +219,8 @@ export class DispatcherConnection {
|
|||||||
id: `call@${id}`,
|
id: `call@${id}`,
|
||||||
...validMetadata,
|
...validMetadata,
|
||||||
objectId: sdkObject?.guid,
|
objectId: sdkObject?.guid,
|
||||||
pageId: sdkObject?.attribution.page?.guid,
|
pageId: sdkObject?.attribution?.page?.guid,
|
||||||
frameId: sdkObject?.attribution.frame?.guid,
|
frameId: sdkObject?.attribution?.frame?.guid,
|
||||||
startTime: monotonicTime(),
|
startTime: monotonicTime(),
|
||||||
endTime: 0,
|
endTime: 0,
|
||||||
type: dispatcher._type,
|
type: dispatcher._type,
|
||||||
|
|||||||
@ -35,14 +35,6 @@ export type Metadata = {
|
|||||||
apiName?: string,
|
apiName?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WaitForEventInfo = {
|
|
||||||
waitId: string,
|
|
||||||
phase: 'before' | 'after' | 'log',
|
|
||||||
event?: string,
|
|
||||||
message?: string,
|
|
||||||
error?: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Point = {
|
export type Point = {
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -604,11 +596,30 @@ export type BrowserStopTracingResult = {
|
|||||||
binary: Binary,
|
binary: Binary,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ----------- EventTarget -----------
|
||||||
|
export type EventTargetInitializer = {};
|
||||||
|
export interface EventTargetChannel extends Channel {
|
||||||
|
waitForEventInfo(params: EventTargetWaitForEventInfoParams, metadata?: Metadata): Promise<EventTargetWaitForEventInfoResult>;
|
||||||
|
}
|
||||||
|
export type EventTargetWaitForEventInfoParams = {
|
||||||
|
info: {
|
||||||
|
waitId: string,
|
||||||
|
phase: 'before' | 'after' | 'log',
|
||||||
|
event?: string,
|
||||||
|
message?: string,
|
||||||
|
error?: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export type EventTargetWaitForEventInfoOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type EventTargetWaitForEventInfoResult = void;
|
||||||
|
|
||||||
// ----------- BrowserContext -----------
|
// ----------- BrowserContext -----------
|
||||||
export type BrowserContextInitializer = {
|
export type BrowserContextInitializer = {
|
||||||
isChromium: boolean,
|
isChromium: boolean,
|
||||||
};
|
};
|
||||||
export interface BrowserContextChannel extends Channel {
|
export interface BrowserContextChannel extends EventTargetChannel {
|
||||||
on(event: 'bindingCall', callback: (params: BrowserContextBindingCallEvent) => void): this;
|
on(event: 'bindingCall', callback: (params: BrowserContextBindingCallEvent) => void): this;
|
||||||
on(event: 'close', callback: (params: BrowserContextCloseEvent) => void): this;
|
on(event: 'close', callback: (params: BrowserContextCloseEvent) => void): this;
|
||||||
on(event: 'page', callback: (params: BrowserContextPageEvent) => void): this;
|
on(event: 'page', callback: (params: BrowserContextPageEvent) => void): this;
|
||||||
@ -868,7 +879,7 @@ export type PageInitializer = {
|
|||||||
isClosed: boolean,
|
isClosed: boolean,
|
||||||
opener?: PageChannel,
|
opener?: PageChannel,
|
||||||
};
|
};
|
||||||
export interface PageChannel extends Channel {
|
export interface PageChannel extends EventTargetChannel {
|
||||||
on(event: 'bindingCall', callback: (params: PageBindingCallEvent) => void): this;
|
on(event: 'bindingCall', callback: (params: PageBindingCallEvent) => void): this;
|
||||||
on(event: 'close', callback: (params: PageCloseEvent) => void): this;
|
on(event: 'close', callback: (params: PageCloseEvent) => void): this;
|
||||||
on(event: 'console', callback: (params: PageConsoleEvent) => void): this;
|
on(event: 'console', callback: (params: PageConsoleEvent) => void): this;
|
||||||
@ -2460,7 +2471,7 @@ export type RemoteAddr = {
|
|||||||
export type WebSocketInitializer = {
|
export type WebSocketInitializer = {
|
||||||
url: string,
|
url: string,
|
||||||
};
|
};
|
||||||
export interface WebSocketChannel extends Channel {
|
export interface WebSocketChannel extends EventTargetChannel {
|
||||||
on(event: 'open', callback: (params: WebSocketOpenEvent) => void): this;
|
on(event: 'open', callback: (params: WebSocketOpenEvent) => void): this;
|
||||||
on(event: 'frameSent', callback: (params: WebSocketFrameSentEvent) => void): this;
|
on(event: 'frameSent', callback: (params: WebSocketFrameSentEvent) => void): this;
|
||||||
on(event: 'frameReceived', callback: (params: WebSocketFrameReceivedEvent) => void): this;
|
on(event: 'frameReceived', callback: (params: WebSocketFrameReceivedEvent) => void): this;
|
||||||
@ -2717,7 +2728,7 @@ export type ElectronLaunchResult = {
|
|||||||
export type ElectronApplicationInitializer = {
|
export type ElectronApplicationInitializer = {
|
||||||
context: BrowserContextChannel,
|
context: BrowserContextChannel,
|
||||||
};
|
};
|
||||||
export interface ElectronApplicationChannel extends Channel {
|
export interface ElectronApplicationChannel extends EventTargetChannel {
|
||||||
on(event: 'close', callback: (params: ElectronApplicationCloseEvent) => void): this;
|
on(event: 'close', callback: (params: ElectronApplicationCloseEvent) => void): this;
|
||||||
browserWindow(params: ElectronApplicationBrowserWindowParams, metadata?: Metadata): Promise<ElectronApplicationBrowserWindowResult>;
|
browserWindow(params: ElectronApplicationBrowserWindowParams, metadata?: Metadata): Promise<ElectronApplicationBrowserWindowResult>;
|
||||||
evaluateExpression(params: ElectronApplicationEvaluateExpressionParams, metadata?: Metadata): Promise<ElectronApplicationEvaluateExpressionResult>;
|
evaluateExpression(params: ElectronApplicationEvaluateExpressionParams, metadata?: Metadata): Promise<ElectronApplicationEvaluateExpressionResult>;
|
||||||
@ -2807,7 +2818,7 @@ export type AndroidDeviceInitializer = {
|
|||||||
model: string,
|
model: string,
|
||||||
serial: string,
|
serial: string,
|
||||||
};
|
};
|
||||||
export interface AndroidDeviceChannel extends Channel {
|
export interface AndroidDeviceChannel extends EventTargetChannel {
|
||||||
on(event: 'webViewAdded', callback: (params: AndroidDeviceWebViewAddedEvent) => void): this;
|
on(event: 'webViewAdded', callback: (params: AndroidDeviceWebViewAddedEvent) => void): this;
|
||||||
on(event: 'webViewRemoved', callback: (params: AndroidDeviceWebViewRemovedEvent) => void): this;
|
on(event: 'webViewRemoved', callback: (params: AndroidDeviceWebViewRemovedEvent) => void): this;
|
||||||
wait(params: AndroidDeviceWaitParams, metadata?: Metadata): Promise<AndroidDeviceWaitResult>;
|
wait(params: AndroidDeviceWaitParams, metadata?: Metadata): Promise<AndroidDeviceWaitResult>;
|
||||||
@ -3237,6 +3248,12 @@ export type SocksSocketEndOptions = {};
|
|||||||
export type SocksSocketEndResult = void;
|
export type SocksSocketEndResult = void;
|
||||||
|
|
||||||
export const commandsWithTracingSnapshots = new Set([
|
export const commandsWithTracingSnapshots = new Set([
|
||||||
|
'EventTarget.waitForEventInfo',
|
||||||
|
'BrowserContext.waitForEventInfo',
|
||||||
|
'Page.waitForEventInfo',
|
||||||
|
'WebSocket.waitForEventInfo',
|
||||||
|
'ElectronApplication.waitForEventInfo',
|
||||||
|
'AndroidDevice.waitForEventInfo',
|
||||||
'Page.goBack',
|
'Page.goBack',
|
||||||
'Page.goForward',
|
'Page.goForward',
|
||||||
'Page.reload',
|
'Page.reload',
|
||||||
@ -3285,7 +3302,9 @@ export const commandsWithTracingSnapshots = new Set([
|
|||||||
'Frame.waitForFunction',
|
'Frame.waitForFunction',
|
||||||
'Frame.waitForSelector',
|
'Frame.waitForSelector',
|
||||||
'JSHandle.evaluateExpression',
|
'JSHandle.evaluateExpression',
|
||||||
|
'ElementHandle.evaluateExpression',
|
||||||
'JSHandle.evaluateExpressionHandle',
|
'JSHandle.evaluateExpressionHandle',
|
||||||
|
'ElementHandle.evaluateExpressionHandle',
|
||||||
'ElementHandle.evalOnSelector',
|
'ElementHandle.evalOnSelector',
|
||||||
'ElementHandle.evalOnSelectorAll',
|
'ElementHandle.evalOnSelectorAll',
|
||||||
'ElementHandle.check',
|
'ElementHandle.check',
|
||||||
|
|||||||
@ -31,21 +31,6 @@ Metadata:
|
|||||||
apiName: string?
|
apiName: string?
|
||||||
|
|
||||||
|
|
||||||
WaitForEventInfo:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
waitId: string
|
|
||||||
phase:
|
|
||||||
type: enum
|
|
||||||
literals:
|
|
||||||
- before
|
|
||||||
- after
|
|
||||||
- log
|
|
||||||
event: string?
|
|
||||||
message: string?
|
|
||||||
error: string?
|
|
||||||
|
|
||||||
|
|
||||||
Point:
|
Point:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -504,10 +489,33 @@ Browser:
|
|||||||
close:
|
close:
|
||||||
|
|
||||||
|
|
||||||
|
EventTarget:
|
||||||
|
type: interface
|
||||||
|
|
||||||
|
commands:
|
||||||
|
waitForEventInfo:
|
||||||
|
parameters:
|
||||||
|
info:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
waitId: string
|
||||||
|
phase:
|
||||||
|
type: enum
|
||||||
|
literals:
|
||||||
|
- before
|
||||||
|
- after
|
||||||
|
- log
|
||||||
|
event: string?
|
||||||
|
message: string?
|
||||||
|
error: string?
|
||||||
|
tracing:
|
||||||
|
snapshot: true
|
||||||
|
|
||||||
BrowserContext:
|
BrowserContext:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
|
extends: EventTarget
|
||||||
|
|
||||||
initializer:
|
initializer:
|
||||||
isChromium: boolean
|
isChromium: boolean
|
||||||
|
|
||||||
@ -691,6 +699,8 @@ BrowserContext:
|
|||||||
Page:
|
Page:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
|
extends: EventTarget
|
||||||
|
|
||||||
initializer:
|
initializer:
|
||||||
mainFrame: Frame
|
mainFrame: Frame
|
||||||
viewportSize:
|
viewportSize:
|
||||||
@ -2128,6 +2138,8 @@ RemoteAddr:
|
|||||||
WebSocket:
|
WebSocket:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
|
extends: EventTarget
|
||||||
|
|
||||||
initializer:
|
initializer:
|
||||||
url: string
|
url: string
|
||||||
|
|
||||||
@ -2346,6 +2358,8 @@ Electron:
|
|||||||
ElectronApplication:
|
ElectronApplication:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
|
extends: EventTarget
|
||||||
|
|
||||||
initializer:
|
initializer:
|
||||||
context: BrowserContext
|
context: BrowserContext
|
||||||
|
|
||||||
@ -2413,6 +2427,8 @@ AndroidSocket:
|
|||||||
AndroidDevice:
|
AndroidDevice:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
|
extends: EventTarget
|
||||||
|
|
||||||
initializer:
|
initializer:
|
||||||
model: string
|
model: string
|
||||||
serial: string
|
serial: string
|
||||||
|
|||||||
@ -43,13 +43,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
stack: tOptional(tArray(tType('StackFrame'))),
|
stack: tOptional(tArray(tType('StackFrame'))),
|
||||||
apiName: tOptional(tString),
|
apiName: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.WaitForEventInfo = tObject({
|
|
||||||
waitId: tString,
|
|
||||||
phase: tEnum(['before', 'after', 'log']),
|
|
||||||
event: tOptional(tString),
|
|
||||||
message: tOptional(tString),
|
|
||||||
error: tOptional(tString),
|
|
||||||
});
|
|
||||||
scheme.Point = tObject({
|
scheme.Point = tObject({
|
||||||
x: tNumber,
|
x: tNumber,
|
||||||
y: tNumber,
|
y: tNumber,
|
||||||
@ -335,6 +328,20 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
categories: tOptional(tArray(tString)),
|
categories: tOptional(tArray(tString)),
|
||||||
});
|
});
|
||||||
scheme.BrowserStopTracingParams = tOptional(tObject({}));
|
scheme.BrowserStopTracingParams = tOptional(tObject({}));
|
||||||
|
scheme.EventTargetWaitForEventInfoParams = tObject({
|
||||||
|
info: tObject({
|
||||||
|
waitId: tString,
|
||||||
|
phase: tEnum(['before', 'after', 'log']),
|
||||||
|
event: tOptional(tString),
|
||||||
|
message: tOptional(tString),
|
||||||
|
error: tOptional(tString),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
scheme.BrowserContextWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
|
scheme.PageWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
|
scheme.WebSocketWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
|
scheme.ElectronApplicationWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
|
scheme.AndroidDeviceWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
scheme.BrowserContextAddCookiesParams = tObject({
|
scheme.BrowserContextAddCookiesParams = tObject({
|
||||||
cookies: tArray(tType('SetNetworkCookie')),
|
cookies: tArray(tType('SetNetworkCookie')),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -133,7 +133,7 @@ export class Tracing implements InstrumentationListener {
|
|||||||
return;
|
return;
|
||||||
if (!this._snapshotter.started())
|
if (!this._snapshotter.started())
|
||||||
return;
|
return;
|
||||||
if (!this._shouldCaptureSnapshot(metadata))
|
if (!shouldCaptureSnapshot(metadata))
|
||||||
return;
|
return;
|
||||||
const snapshotName = `${name}@${metadata.id}`;
|
const snapshotName = `${name}@${metadata.id}`;
|
||||||
metadata.snapshots.push({ title: name, snapshotName });
|
metadata.snapshots.push({ title: name, snapshotName });
|
||||||
@ -162,7 +162,7 @@ export class Tracing implements InstrumentationListener {
|
|||||||
}
|
}
|
||||||
pendingCall.afterSnapshot = this._captureSnapshot('after', sdkObject, metadata);
|
pendingCall.afterSnapshot = this._captureSnapshot('after', sdkObject, metadata);
|
||||||
await pendingCall.afterSnapshot;
|
await pendingCall.afterSnapshot;
|
||||||
const event: trace.ActionTraceEvent = { type: 'action', metadata, hasSnapshot: this._shouldCaptureSnapshot(metadata) };
|
const event: trace.ActionTraceEvent = { type: 'action', metadata, hasSnapshot: shouldCaptureSnapshot(metadata) };
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
this._pendingCalls.delete(metadata.id);
|
this._pendingCalls.delete(metadata.id);
|
||||||
}
|
}
|
||||||
@ -225,8 +225,8 @@ export class Tracing implements InstrumentationListener {
|
|||||||
await fs.promises.appendFile(this._traceFile!, JSON.stringify(event) + '\n');
|
await fs.promises.appendFile(this._traceFile!, JSON.stringify(event) + '\n');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _shouldCaptureSnapshot(metadata: CallMetadata): boolean {
|
function shouldCaptureSnapshot(metadata: CallMetadata): boolean {
|
||||||
return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method);
|
return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -43,9 +43,8 @@ export class TraceModel {
|
|||||||
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
|
||||||
for (const event of events)
|
for (const event of events)
|
||||||
this.appendEvent(event);
|
this.appendEvent(event);
|
||||||
const actions: trace.ActionTraceEvent[] = [];
|
|
||||||
for (const page of this.contextEntry!.pages)
|
for (const page of this.contextEntry!.pages)
|
||||||
actions.push(...page.actions);
|
page.actions.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime);
|
||||||
this.contextEntry!.resources = snapshotStorage.resources();
|
this.contextEntry!.resources = snapshotStorage.resources();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +54,7 @@ export class TraceModel {
|
|||||||
pageEntry = {
|
pageEntry = {
|
||||||
actions: [],
|
actions: [],
|
||||||
events: [],
|
events: [],
|
||||||
|
objects: {},
|
||||||
screencastFrames: [],
|
screencastFrames: [],
|
||||||
};
|
};
|
||||||
this.pageEntries.set(pageId, pageEntry);
|
this.pageEntries.set(pageId, pageEntry);
|
||||||
@ -83,8 +83,12 @@ export class TraceModel {
|
|||||||
}
|
}
|
||||||
case 'event': {
|
case 'event': {
|
||||||
const metadata = event.metadata;
|
const metadata = event.metadata;
|
||||||
if (metadata.pageId)
|
if (metadata.pageId) {
|
||||||
|
if (metadata.method === '__create__')
|
||||||
|
this._pageEntry(metadata.pageId).objects[metadata.params.guid] = metadata.params.initializer;
|
||||||
|
else
|
||||||
this._pageEntry(metadata.pageId).events.push(event);
|
this._pageEntry(metadata.pageId).events.push(event);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'resource-snapshot':
|
case 'resource-snapshot':
|
||||||
@ -113,6 +117,7 @@ export type ContextEntry = {
|
|||||||
export type PageEntry = {
|
export type PageEntry = {
|
||||||
actions: trace.ActionTraceEvent[];
|
actions: trace.ActionTraceEvent[];
|
||||||
events: trace.ActionTraceEvent[];
|
events: trace.ActionTraceEvent[];
|
||||||
|
objects: { [ket: string]: any };
|
||||||
screencastFrames: {
|
screencastFrames: {
|
||||||
sha1: string,
|
sha1: string,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
|
|||||||
@ -141,7 +141,10 @@ export class TraceViewer {
|
|||||||
});
|
});
|
||||||
await context.extendInjectedScript('main', consoleApiSource.source);
|
await context.extendInjectedScript('main', consoleApiSource.source);
|
||||||
const [page] = context.pages();
|
const [page] = context.pages();
|
||||||
|
if (isUnderTest())
|
||||||
page.on('close', () => context.close(internalCallMetadata()).catch(() => {}));
|
page.on('close', () => context.close(internalCallMetadata()).catch(() => {}));
|
||||||
|
else
|
||||||
|
page.on('close', () => process.exit());
|
||||||
await page.mainFrame().goto(internalCallMetadata(), urlPrefix + '/traceviewer/traceViewer/index.html');
|
await page.mainFrame().goto(internalCallMetadata(), urlPrefix + '/traceviewer/traceViewer/index.html');
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/web/third_party/vscode/codicon.css
vendored
1
src/web/third_party/vscode/codicon.css
vendored
@ -22,6 +22,7 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codicon-blank:before { content: '\81'; }
|
||||||
.codicon-add:before { content: '\ea60'; }
|
.codicon-add:before { content: '\ea60'; }
|
||||||
.codicon-plus:before { content: '\ea60'; }
|
.codicon-plus:before { content: '\ea60'; }
|
||||||
.codicon-gist-new:before { content: '\ea60'; }
|
.codicon-gist-new:before { content: '\ea60'; }
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export const FilmStrip: React.FunctionComponent<{
|
|||||||
if (previewPoint !== undefined && screencastFrames) {
|
if (previewPoint !== undefined && screencastFrames) {
|
||||||
const previewTime = boundaries.minimum + (boundaries.maximum - boundaries.minimum) * previewPoint.x / measure.width;
|
const previewTime = boundaries.minimum + (boundaries.maximum - boundaries.minimum) * previewPoint.x / measure.width;
|
||||||
previewImage = screencastFrames[upperBound(screencastFrames, previewTime, timeComparator) - 1];
|
previewImage = screencastFrames[upperBound(screencastFrames, previewTime, timeComparator) - 1];
|
||||||
previewSize = inscribe({width: previewImage.width, height: previewImage.height}, { width: 600, height: 600 });
|
previewSize = previewImage ? inscribe({width: previewImage.width, height: previewImage.height}, { width: 600, height: 600 }) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className='film-strip' ref={ref}>{
|
return <div className='film-strip' ref={ref}>{
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-element {
|
.tab-element {
|
||||||
padding: 2px 6px 0 6px;
|
padding: 2px 12px 0 12px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -53,7 +53,6 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border-bottom: 3px solid transparent;
|
border-bottom: 3px solid transparent;
|
||||||
width: 80px;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -170,8 +170,8 @@ export const Timeline: React.FunctionComponent<{
|
|||||||
return <div key={index}
|
return <div key={index}
|
||||||
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
|
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
|
||||||
style={{
|
style={{
|
||||||
left: bar.leftPosition + 'px',
|
left: bar.leftPosition,
|
||||||
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
|
maxWidth: 100,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{bar.label}
|
{bar.label}
|
||||||
|
|||||||
@ -54,6 +54,9 @@ export const Workbench: React.FunctionComponent<{
|
|||||||
const snapshotSize = context.options.viewport || { width: 1280, height: 720 };
|
const snapshotSize = context.options.viewport || { width: 1280, height: 720 };
|
||||||
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
const boundaries = { minimum: context.startTime, maximum: context.endTime };
|
||||||
|
|
||||||
|
// Leave some nice free space on the right hand side.
|
||||||
|
boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20;
|
||||||
|
|
||||||
return <div className='vbox workbench'>
|
return <div className='vbox workbench'>
|
||||||
<div className='hbox header'>
|
<div className='hbox header'>
|
||||||
<div className='logo'>🎭</div>
|
<div className='logo'>🎭</div>
|
||||||
|
|||||||
@ -24,8 +24,8 @@ class TraceViewerPage {
|
|||||||
constructor(public page: Page) {}
|
constructor(public page: Page) {}
|
||||||
|
|
||||||
async actionTitles() {
|
async actionTitles() {
|
||||||
await this.page.waitForSelector('.action-title');
|
await this.page.waitForSelector('.action-title:visible');
|
||||||
return await this.page.$$eval('.action-title', ee => ee.map(e => e.textContent));
|
return await this.page.$$eval('.action-title:visible', ee => ee.map(e => e.textContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectAction(title: string) {
|
async selectAction(title: string) {
|
||||||
@ -33,7 +33,20 @@ class TraceViewerPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async logLines() {
|
async logLines() {
|
||||||
return await this.page.$$eval('.log-line', ee => ee.map(e => e.textContent));
|
await this.page.waitForSelector('.log-line:visible');
|
||||||
|
return await this.page.$$eval('.log-line:visible', ee => ee.map(e => e.textContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
async eventBars() {
|
||||||
|
await this.page.waitForSelector('.timeline-bar.event:visible');
|
||||||
|
const list = await this.page.$$eval('.timeline-bar.event:visible', ee => ee.map(e => e.className));
|
||||||
|
const set = new Set<string>();
|
||||||
|
for (const item of list) {
|
||||||
|
for (const className of item.split(' '))
|
||||||
|
set.add(className);
|
||||||
|
}
|
||||||
|
const result = [...set];
|
||||||
|
return result.sort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +73,15 @@ test.beforeAll(async ({ browser }, workerInfo) => {
|
|||||||
await page.goto('data:text/html,<html>Hello world</html>');
|
await page.goto('data:text/html,<html>Hello world</html>');
|
||||||
await page.setContent('<button>Click</button>');
|
await page.setContent('<button>Click</button>');
|
||||||
await page.click('"Click"');
|
await page.click('"Click"');
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.waitForTimeout(200).then(() => page.goto('data:text/html,<html>Hello world 2</html>'))
|
||||||
|
]);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
console.log('Log');
|
||||||
|
console.warn('Warning');
|
||||||
|
console.error('Error');
|
||||||
|
});
|
||||||
await page.close();
|
await page.close();
|
||||||
traceFile = path.join(workerInfo.project.outputDir, 'trace.zip');
|
traceFile = path.join(workerInfo.project.outputDir, 'trace.zip');
|
||||||
await context.tracing.stop({ path: traceFile });
|
await context.tracing.stop({ path: traceFile });
|
||||||
@ -72,7 +94,14 @@ test('should show empty trace viewer', async ({ showTraceViewer }, testInfo) =>
|
|||||||
|
|
||||||
test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
||||||
const traceViewer = await showTraceViewer(traceFile);
|
const traceViewer = await showTraceViewer(traceFile);
|
||||||
expect(await traceViewer.actionTitles()).toEqual(['page.goto', 'page.setContent', 'page.click']);
|
expect(await traceViewer.actionTitles()).toEqual([
|
||||||
|
'page.goto',
|
||||||
|
'page.setContent',
|
||||||
|
'page.click',
|
||||||
|
'page.waitForNavigation',
|
||||||
|
'page.goto',
|
||||||
|
'page.evaluate'
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should contain action log', async ({ showTraceViewer }) => {
|
test('should contain action log', async ({ showTraceViewer }) => {
|
||||||
@ -84,3 +113,9 @@ test('should contain action log', async ({ showTraceViewer }) => {
|
|||||||
expect(logLines).toContain('attempting click action');
|
expect(logLines).toContain('attempting click action');
|
||||||
expect(logLines).toContain(' click action done');
|
expect(logLines).toContain(' click action done');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render events', async ({ showTraceViewer }) => {
|
||||||
|
const traceViewer = await showTraceViewer(traceFile);
|
||||||
|
const events = await traceViewer.eventBars();
|
||||||
|
expect(events).toContain('page_console');
|
||||||
|
});
|
||||||
|
|||||||
@ -187,6 +187,18 @@ for (const [name, value] of Object.entries(protocol)) {
|
|||||||
mixins.set(name, value);
|
mixins.set(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const derivedClasses = new Map();
|
||||||
|
for (const [name, item] of Object.entries(protocol)) {
|
||||||
|
if (item.type === 'interface' && item.extends) {
|
||||||
|
let items = derivedClasses.get(item.extends);
|
||||||
|
if (!items) {
|
||||||
|
items = [];
|
||||||
|
derivedClasses.set(item.extends, items);
|
||||||
|
}
|
||||||
|
items.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const [name, item] of Object.entries(protocol)) {
|
for (const [name, item] of Object.entries(protocol)) {
|
||||||
if (item.type === 'interface') {
|
if (item.type === 'interface') {
|
||||||
const channelName = name;
|
const channelName = name;
|
||||||
@ -210,8 +222,11 @@ for (const [name, item] of Object.entries(protocol)) {
|
|||||||
for (let [methodName, method] of Object.entries(item.commands || {})) {
|
for (let [methodName, method] of Object.entries(item.commands || {})) {
|
||||||
if (method === null)
|
if (method === null)
|
||||||
method = {};
|
method = {};
|
||||||
if (method.tracing && method.tracing.snapshot)
|
if (method.tracing && method.tracing.snapshot) {
|
||||||
tracingSnapshots.push(name + '.' + methodName);
|
tracingSnapshots.push(name + '.' + methodName);
|
||||||
|
for (const derived of derivedClasses.get(name) || [])
|
||||||
|
tracingSnapshots.push(derived + '.' + methodName);
|
||||||
|
}
|
||||||
const parameters = objectType(method.parameters || {}, '');
|
const parameters = objectType(method.parameters || {}, '');
|
||||||
const paramsName = `${channelName}${titleCase(methodName)}Params`;
|
const paramsName = `${channelName}${titleCase(methodName)}Params`;
|
||||||
const optionsName = `${channelName}${titleCase(methodName)}Options`;
|
const optionsName = `${channelName}${titleCase(methodName)}Options`;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user