chore: iterate towards recording into trace (3) (#32718)
This commit is contained in:
parent
bef1e990ac
commit
dfb3fdf217
|
|
@ -397,7 +397,7 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
|
||||||
process.stdout.write('\n-------------8<-------------\n');
|
process.stdout.write('\n-------------8<-------------\n');
|
||||||
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
|
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
|
||||||
if (autoExitCondition && text.includes(autoExitCondition))
|
if (autoExitCondition && text.includes(autoExitCondition))
|
||||||
Promise.all(context.pages().map(async p => p.close()));
|
closeBrowser();
|
||||||
};
|
};
|
||||||
// Make sure we exit abnormally when browser crashes.
|
// Make sure we exit abnormally when browser crashes.
|
||||||
const logs: string[] = [];
|
const logs: string[] = [];
|
||||||
|
|
@ -504,7 +504,7 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
|
||||||
if (hasPage)
|
if (hasPage)
|
||||||
return;
|
return;
|
||||||
// Avoid the error when the last page is closed because the browser has been closed.
|
// Avoid the error when the last page is closed because the browser has been closed.
|
||||||
closeBrowser().catch(e => null);
|
closeBrowser().catch(() => {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
process.on('SIGINT', async () => {
|
process.on('SIGINT', async () => {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export class Dialog extends SdkObject {
|
||||||
this._onHandle = onHandle;
|
this._onHandle = onHandle;
|
||||||
this._defaultValue = defaultValue || '';
|
this._defaultValue = defaultValue || '';
|
||||||
this._page._frameManager.dialogDidOpen(this);
|
this._page._frameManager.dialogDidOpen(this);
|
||||||
|
this.instrumentation.onDialog(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
page() {
|
page() {
|
||||||
|
|
|
||||||
|
|
@ -35,16 +35,25 @@ export class Download {
|
||||||
this._suggestedFilename = suggestedFilename;
|
this._suggestedFilename = suggestedFilename;
|
||||||
page._browserContext._downloads.add(this);
|
page._browserContext._downloads.add(this);
|
||||||
if (suggestedFilename !== undefined)
|
if (suggestedFilename !== undefined)
|
||||||
this._page.emit(Page.Events.Download, this);
|
this._fireDownloadEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
page(): Page {
|
||||||
|
return this._page;
|
||||||
}
|
}
|
||||||
|
|
||||||
_filenameSuggested(suggestedFilename: string) {
|
_filenameSuggested(suggestedFilename: string) {
|
||||||
assert(this._suggestedFilename === undefined);
|
assert(this._suggestedFilename === undefined);
|
||||||
this._suggestedFilename = suggestedFilename;
|
this._suggestedFilename = suggestedFilename;
|
||||||
this._page.emit(Page.Events.Download, this);
|
this._fireDownloadEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestedFilename(): string {
|
suggestedFilename(): string {
|
||||||
return this._suggestedFilename!;
|
return this._suggestedFilename!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _fireDownloadEvent() {
|
||||||
|
this._page.instrumentation.onDownload(this._page, this);
|
||||||
|
this._page.emit(Page.Events.Download, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ export type Attribution = {
|
||||||
};
|
};
|
||||||
|
|
||||||
import type { CallMetadata } from '@protocol/callMetadata';
|
import type { CallMetadata } from '@protocol/callMetadata';
|
||||||
|
import type { Dialog } from './dialog';
|
||||||
|
import type { Download } from './download';
|
||||||
export type { CallMetadata } from '@protocol/callMetadata';
|
export type { CallMetadata } from '@protocol/callMetadata';
|
||||||
|
|
||||||
export class SdkObject extends EventEmitter {
|
export class SdkObject extends EventEmitter {
|
||||||
|
|
@ -62,6 +64,8 @@ export interface Instrumentation {
|
||||||
onPageClose(page: Page): void;
|
onPageClose(page: Page): void;
|
||||||
onBrowserOpen(browser: Browser): void;
|
onBrowserOpen(browser: Browser): void;
|
||||||
onBrowserClose(browser: Browser): void;
|
onBrowserClose(browser: Browser): void;
|
||||||
|
onDialog(dialog: Dialog): void;
|
||||||
|
onDownload(page: Page, download: Download): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstrumentationListener {
|
export interface InstrumentationListener {
|
||||||
|
|
@ -73,6 +77,8 @@ export interface InstrumentationListener {
|
||||||
onPageClose?(page: Page): void;
|
onPageClose?(page: Page): void;
|
||||||
onBrowserOpen?(browser: Browser): void;
|
onBrowserOpen?(browser: Browser): void;
|
||||||
onBrowserClose?(browser: Browser): void;
|
onBrowserClose?(browser: Browser): void;
|
||||||
|
onDialog?(dialog: Dialog): void;
|
||||||
|
onDownload?(page: Page, download: Download): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInstrumentation(): Instrumentation {
|
export function createInstrumentation(): Instrumentation {
|
||||||
|
|
|
||||||
|
|
@ -162,8 +162,10 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
}).toString(), { isFunction: true }, sources).catch(() => {});
|
}).toString(), { isFunction: true }, sources).catch(() => {});
|
||||||
|
|
||||||
// Testing harness for runCLI mode.
|
// Testing harness for runCLI mode.
|
||||||
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length)
|
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) {
|
||||||
(process as any)._didSetSourcesForTest(sources[0].text);
|
if ((process as any)._didSetSourcesForTest(sources[0].text))
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -21,77 +21,97 @@ import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFro
|
||||||
import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer';
|
import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer';
|
||||||
import type { TraceViewerServerOptions } from '../trace/viewer/traceViewer';
|
import type { TraceViewerServerOptions } from '../trace/viewer/traceViewer';
|
||||||
import type { BrowserContext } from '../browserContext';
|
import type { BrowserContext } from '../browserContext';
|
||||||
import { gracefullyProcessExitDoNotHang } from '../../utils/processLauncher';
|
import type { HttpServer, Transport } from '../../utils/httpServer';
|
||||||
import type { Transport } from '../../utils/httpServer';
|
import type { Page } from '../page';
|
||||||
|
import { ManualPromise } from '../../utils/manualPromise';
|
||||||
|
|
||||||
export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp {
|
export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp {
|
||||||
readonly wsEndpointForTest: string | undefined;
|
readonly wsEndpointForTest: string | undefined;
|
||||||
private _recorder: IRecorder;
|
private _transport: RecorderTransport;
|
||||||
private _transport: Transport;
|
private _tracePage: Page;
|
||||||
|
private _traceServer: HttpServer;
|
||||||
|
|
||||||
static factory(context: BrowserContext): IRecorderAppFactory {
|
static factory(context: BrowserContext): IRecorderAppFactory {
|
||||||
return async (recorder: IRecorder) => {
|
return async (recorder: IRecorder) => {
|
||||||
const transport = new RecorderTransport();
|
const transport = new RecorderTransport();
|
||||||
const trace = path.join(context._browser.options.tracesDir, 'trace');
|
const trace = path.join(context._browser.options.tracesDir, 'trace');
|
||||||
const wsEndpointForTest = await openApp(trace, { transport, headless: !context._browser.options.headful });
|
const { wsEndpointForTest, tracePage, traceServer } = await openApp(trace, { transport, headless: !context._browser.options.headful });
|
||||||
return new RecorderInTraceViewer(context, recorder, transport, wsEndpointForTest);
|
return new RecorderInTraceViewer(transport, tracePage, traceServer, wsEndpointForTest);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: BrowserContext, recorder: IRecorder, transport: Transport, wsEndpointForTest: string | undefined) {
|
constructor(transport: RecorderTransport, tracePage: Page, traceServer: HttpServer, wsEndpointForTest: string | undefined) {
|
||||||
super();
|
super();
|
||||||
this._recorder = recorder;
|
|
||||||
this._transport = transport;
|
this._transport = transport;
|
||||||
|
this._tracePage = tracePage;
|
||||||
|
this._traceServer = traceServer;
|
||||||
this.wsEndpointForTest = wsEndpointForTest;
|
this.wsEndpointForTest = wsEndpointForTest;
|
||||||
|
this._tracePage.once('close', () => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
this._transport.sendEvent?.('close', {});
|
await this._tracePage.context().close({ reason: 'Recorder window closed' });
|
||||||
|
await this._traceServer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPaused(paused: boolean): Promise<void> {
|
async setPaused(paused: boolean): Promise<void> {
|
||||||
this._transport.sendEvent?.('setPaused', { paused });
|
this._transport.deliverEvent('setPaused', { paused });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMode(mode: Mode): Promise<void> {
|
async setMode(mode: Mode): Promise<void> {
|
||||||
this._transport.sendEvent?.('setMode', { mode });
|
this._transport.deliverEvent('setMode', { mode });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setFile(file: string): Promise<void> {
|
async setFile(file: string): Promise<void> {
|
||||||
this._transport.sendEvent?.('setFileIfNeeded', { file });
|
this._transport.deliverEvent('setFileIfNeeded', { file });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
||||||
this._transport.sendEvent?.('setSelector', { selector, userGesture });
|
this._transport.deliverEvent('setSelector', { selector, userGesture });
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
||||||
this._transport.sendEvent?.('updateCallLogs', { callLogs });
|
this._transport.deliverEvent('updateCallLogs', { callLogs });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSources(sources: Source[]): Promise<void> {
|
async setSources(sources: Source[]): Promise<void> {
|
||||||
this._transport.sendEvent?.('setSources', { sources });
|
this._transport.deliverEvent('setSources', { sources });
|
||||||
|
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) {
|
||||||
|
if ((process as any)._didSetSourcesForTest(sources[0].text))
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<string | undefined> {
|
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<{ wsEndpointForTest: string | undefined, tracePage: Page, traceServer: HttpServer }> {
|
||||||
const server = await startTraceViewerServer(options);
|
const traceServer = await startTraceViewerServer(options);
|
||||||
await installRootRedirect(server, [trace], { ...options, webApp: 'recorder.html' });
|
await installRootRedirect(traceServer, [trace], { ...options, webApp: 'recorder.html' });
|
||||||
const page = await openTraceViewerApp(server.urlPrefix('precise'), 'chromium', options);
|
const page = await openTraceViewerApp(traceServer.urlPrefix('precise'), 'chromium', options);
|
||||||
page.on('close', () => gracefullyProcessExitDoNotHang(0));
|
return { wsEndpointForTest: page.context()._browser.options.wsEndpoint, tracePage: page, traceServer };
|
||||||
return page.context()._browser.options.wsEndpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecorderTransport implements Transport {
|
class RecorderTransport implements Transport {
|
||||||
|
private _connected = new ManualPromise<void>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async dispatch(method: string, params: any) {
|
onconnect() {
|
||||||
|
this._connected.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispatch(method: string, params: any): Promise<any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onclose() {
|
onclose() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deliverEvent(method: string, params: any) {
|
||||||
|
this._connected.then(() => this.sendEvent?.(method, params));
|
||||||
|
}
|
||||||
|
|
||||||
sendEvent?: (method: string, params: any) => void;
|
sendEvent?: (method: string, params: any) => void;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import type * as trace from '@trace/trace';
|
||||||
import { fromKeyboardModifiers, toKeyboardModifiers } from '../codegen/language';
|
import { fromKeyboardModifiers, toKeyboardModifiers } from '../codegen/language';
|
||||||
import { serializeExpectedTextValues } from '../../utils/expectUtils';
|
import { serializeExpectedTextValues } from '../../utils/expectUtils';
|
||||||
import { createGuid, monotonicTime } from '../../utils';
|
import { createGuid, monotonicTime } from '../../utils';
|
||||||
import { serializeValue } from '../../protocol/serializers';
|
import { parseSerializedValue, serializeValue } from '../../protocol/serializers';
|
||||||
import type { SmartKeyboardModifier } from '../types';
|
import type { SmartKeyboardModifier } from '../types';
|
||||||
|
|
||||||
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
|
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
|
||||||
|
|
@ -158,7 +158,7 @@ export function traceParamsForAction(actionInContext: ActionInContext): { method
|
||||||
const params: channels.FrameExpectParams = {
|
const params: channels.FrameExpectParams = {
|
||||||
selector: action.selector,
|
selector: action.selector,
|
||||||
expression: 'to.be.checked',
|
expression: 'to.be.checked',
|
||||||
isNot: action.checked,
|
isNot: !action.checked,
|
||||||
};
|
};
|
||||||
return { method: 'expect', params };
|
return { method: 'expect', params };
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +166,7 @@ export function traceParamsForAction(actionInContext: ActionInContext): { method
|
||||||
const params: channels.FrameExpectParams = {
|
const params: channels.FrameExpectParams = {
|
||||||
selector,
|
selector,
|
||||||
expression: 'to.have.text',
|
expression: 'to.have.text',
|
||||||
expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }),
|
expectedText: serializeExpectedTextValues([action.text], { matchSubstring: action.substring, normalizeWhiteSpace: true }),
|
||||||
isNot: false,
|
isNot: false,
|
||||||
};
|
};
|
||||||
return { method: 'expect', params };
|
return { method: 'expect', params };
|
||||||
|
|
@ -195,6 +195,7 @@ export function callMetadataForAction(pageAliases: Map<Page, string>, actionInCo
|
||||||
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
||||||
const { action } = actionInContext;
|
const { action } = actionInContext;
|
||||||
const { method, params } = traceParamsForAction(actionInContext);
|
const { method, params } = traceParamsForAction(actionInContext);
|
||||||
|
|
||||||
const callMetadata: CallMetadata = {
|
const callMetadata: CallMetadata = {
|
||||||
id: `call@${createGuid()}`,
|
id: `call@${createGuid()}`,
|
||||||
stepId: `recorder@${createGuid()}`,
|
stepId: `recorder@${createGuid()}`,
|
||||||
|
|
@ -215,38 +216,70 @@ export function callMetadataForAction(pageAliases: Map<Page, string>, actionInCo
|
||||||
export function traceEventsToAction(events: trace.TraceEvent[]): ActionInContext[] {
|
export function traceEventsToAction(events: trace.TraceEvent[]): ActionInContext[] {
|
||||||
const result: ActionInContext[] = [];
|
const result: ActionInContext[] = [];
|
||||||
const pageAliases = new Map<string, string>();
|
const pageAliases = new Map<string, string>();
|
||||||
|
let lastDownloadOrdinal = 0;
|
||||||
|
let lastDialogOrdinal = 0;
|
||||||
|
|
||||||
|
const addSignal = (signal: actions.Signal) => {
|
||||||
|
const lastAction = result[result.length - 1];
|
||||||
|
if (!lastAction)
|
||||||
|
return;
|
||||||
|
lastAction.action.signals.push(signal);
|
||||||
|
};
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
if (event.type === 'event' && event.class === 'BrowserContext' && event.method === 'page') {
|
if (event.type === 'event' && event.class === 'BrowserContext') {
|
||||||
const pageAlias = 'page' + pageAliases.size;
|
const { method, params } = event;
|
||||||
pageAliases.set(event.params.pageId, pageAlias);
|
if (method === 'page') {
|
||||||
const lastAction = result[result.length - 1];
|
const pageAlias = 'page' + (pageAliases.size || '');
|
||||||
lastAction.action.signals.push({
|
pageAliases.set(params.pageId, pageAlias);
|
||||||
name: 'popup',
|
addSignal({
|
||||||
popupAlias: pageAlias,
|
name: 'popup',
|
||||||
});
|
popupAlias: pageAlias,
|
||||||
result.push({
|
});
|
||||||
frame: { pageAlias, framePath: [] },
|
result.push({
|
||||||
action: {
|
frame: { pageAlias, framePath: [] },
|
||||||
name: 'openPage',
|
action: {
|
||||||
url: '',
|
name: 'openPage',
|
||||||
signals: [],
|
url: '',
|
||||||
},
|
signals: [],
|
||||||
timestamp: event.time,
|
},
|
||||||
});
|
timestamp: event.time,
|
||||||
continue;
|
});
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.type === 'event' && event.class === 'BrowserContext' && event.method === 'pageClosed') {
|
if (method === 'pageClosed') {
|
||||||
const pageAlias = pageAliases.get(event.params.pageId) || 'page';
|
const pageAlias = pageAliases.get(event.params.pageId) || 'page';
|
||||||
result.push({
|
result.push({
|
||||||
frame: { pageAlias, framePath: [] },
|
frame: { pageAlias, framePath: [] },
|
||||||
action: {
|
action: {
|
||||||
name: 'closePage',
|
name: 'closePage',
|
||||||
signals: [],
|
signals: [],
|
||||||
},
|
},
|
||||||
timestamp: event.time,
|
timestamp: event.time,
|
||||||
});
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'download') {
|
||||||
|
const downloadAlias = lastDownloadOrdinal ? String(lastDownloadOrdinal) : '';
|
||||||
|
++lastDownloadOrdinal;
|
||||||
|
addSignal({
|
||||||
|
name: 'download',
|
||||||
|
downloadAlias,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'dialog') {
|
||||||
|
const dialogAlias = lastDialogOrdinal ? String(lastDialogOrdinal) : '';
|
||||||
|
++lastDialogOrdinal;
|
||||||
|
addSignal({
|
||||||
|
name: 'dialog',
|
||||||
|
dialogAlias,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,6 +422,67 @@ export function traceEventsToAction(events: trace.TraceEvent[]): ActionInContext
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (method === 'expect') {
|
||||||
|
const params = untypedParams as channels.FrameExpectParams;
|
||||||
|
if (params.expression === 'to.have.text') {
|
||||||
|
const entry = params.expectedText?.[0];
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias, framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'assertText',
|
||||||
|
selector: params.selector,
|
||||||
|
signals: [],
|
||||||
|
text: entry?.string!,
|
||||||
|
substring: !!entry?.matchSubstring,
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.expression === 'to.have.value') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias, framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'assertValue',
|
||||||
|
selector: params.selector,
|
||||||
|
signals: [],
|
||||||
|
value: parseSerializedValue(params.expectedValue!.value, params.expectedValue!.handles),
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.expression === 'to.be.checked') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias, framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'assertChecked',
|
||||||
|
selector: params.selector,
|
||||||
|
signals: [],
|
||||||
|
checked: !params.isNot,
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.expression === 'to.be.visible') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias, framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'assertVisible',
|
||||||
|
selector: params.selector,
|
||||||
|
signals: [],
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ import { Snapshotter } from './snapshotter';
|
||||||
import type { ConsoleMessage } from '../../console';
|
import type { ConsoleMessage } from '../../console';
|
||||||
import { Dispatcher } from '../../dispatchers/dispatcher';
|
import { Dispatcher } from '../../dispatchers/dispatcher';
|
||||||
import { serializeError } from '../../errors';
|
import { serializeError } from '../../errors';
|
||||||
|
import type { Dialog } from '../../dialog';
|
||||||
|
import type { Download } from '../../download';
|
||||||
|
|
||||||
const version: trace.VERSION = 7;
|
const version: trace.VERSION = 7;
|
||||||
|
|
||||||
|
|
@ -454,6 +456,28 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDialog(dialog: Dialog) {
|
||||||
|
const event: trace.EventTraceEvent = {
|
||||||
|
type: 'event',
|
||||||
|
time: monotonicTime(),
|
||||||
|
class: 'BrowserContext',
|
||||||
|
method: 'dialog',
|
||||||
|
params: { pageId: dialog.page().guid, type: dialog.type(), message: dialog.message(), defaultValue: dialog.defaultValue() },
|
||||||
|
};
|
||||||
|
this._appendTraceEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDownload(page: Page, download: Download) {
|
||||||
|
const event: trace.EventTraceEvent = {
|
||||||
|
type: 'event',
|
||||||
|
time: monotonicTime(),
|
||||||
|
class: 'BrowserContext',
|
||||||
|
method: 'download',
|
||||||
|
params: { pageId: page.guid, url: download.url, suggestedFilename: download.suggestedFilename() },
|
||||||
|
};
|
||||||
|
this._appendTraceEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
onPageOpen(page: Page) {
|
onPageOpen(page: Page) {
|
||||||
const event: trace.EventTraceEvent = {
|
const event: trace.EventTraceEvent = {
|
||||||
type: 'event',
|
type: 'event',
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,9 @@ class StdinServer implements Transport {
|
||||||
process.stdin.on('close', () => gracefullyProcessExitDoNotHang(0));
|
process.stdin.on('close', () => gracefullyProcessExitDoNotHang(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onconnect() {
|
||||||
|
}
|
||||||
|
|
||||||
async dispatch(method: string, params: any) {
|
async dispatch(method: string, params: any) {
|
||||||
if (method === 'initialize') {
|
if (method === 'initialize') {
|
||||||
if (this._traceUrl)
|
if (this._traceUrl)
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@ export type ServerRouteHandler = (request: http.IncomingMessage, response: http.
|
||||||
|
|
||||||
export type Transport = {
|
export type Transport = {
|
||||||
sendEvent?: (method: string, params: any) => void;
|
sendEvent?: (method: string, params: any) => void;
|
||||||
dispatch: (method: string, params: any) => Promise<any>;
|
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
|
onconnect: () => void;
|
||||||
|
dispatch: (method: string, params: any) => Promise<any>;
|
||||||
onclose: () => void;
|
onclose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,6 +83,7 @@ export class HttpServer {
|
||||||
this._wsGuid = guid || createGuid();
|
this._wsGuid = guid || createGuid();
|
||||||
const wss = new wsServer({ server: this._server, path: '/' + this._wsGuid });
|
const wss = new wsServer({ server: this._server, path: '/' + this._wsGuid });
|
||||||
wss.on('connection', ws => {
|
wss.on('connection', ws => {
|
||||||
|
transport.onconnect();
|
||||||
transport.sendEvent = (method, params) => ws.send(JSON.stringify({ method, params }));
|
transport.sendEvent = (method, params) => ws.send(JSON.stringify({ method, params }));
|
||||||
transport.close = () => ws.close();
|
transport.close = () => ws.close();
|
||||||
ws.on('message', async message => {
|
ws.on('message', async message => {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ export class TestServerDispatcher implements TestServerInterface {
|
||||||
constructor(configLocation: ConfigLocation) {
|
constructor(configLocation: ConfigLocation) {
|
||||||
this._configLocation = configLocation;
|
this._configLocation = configLocation;
|
||||||
this.transport = {
|
this.transport = {
|
||||||
|
onconnect: () => {},
|
||||||
dispatch: (method, params) => (this as any)[method](params),
|
dispatch: (method, params) => (this as any)[method](params),
|
||||||
onclose: () => {
|
onclose: () => {
|
||||||
if (this._closeOnDisconnect)
|
if (this._closeOnDisconnect)
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@ export const RecorderView: React.FunctionComponent = () => {
|
||||||
connection.setMode('recording');
|
connection.setMode('recording');
|
||||||
}, [connection]);
|
}, [connection]);
|
||||||
|
|
||||||
window.playwrightSourcesEchoForTest = sources;
|
|
||||||
|
|
||||||
return <div className='vbox workbench-loader'>
|
return <div className='vbox workbench-loader'>
|
||||||
<TraceView
|
<TraceView
|
||||||
traceLocation={trace}
|
traceLocation={trace}
|
||||||
|
|
@ -165,6 +163,7 @@ class Connection {
|
||||||
if (method === 'setSources') {
|
if (method === 'setSources') {
|
||||||
const { sources } = params as { sources: Source[] };
|
const { sources } = params as { sources: Source[] };
|
||||||
this._options.setSources(sources);
|
this._options.setSources(sources);
|
||||||
|
window.playwrightSourcesEchoForTest = sources;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue