Skip to content

Commit

Permalink
chore: iterate towards recording into trace (3) (#32718)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Sep 20, 2024
1 parent bef1e99 commit dfb3fdf
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 63 deletions.
4 changes: 2 additions & 2 deletions packages/playwright-core/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
process.stdout.write('\n-------------8<-------------\n');
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
if (autoExitCondition && text.includes(autoExitCondition))
Promise.all(context.pages().map(async p => p.close()));
closeBrowser();
};
// Make sure we exit abnormally when browser crashes.
const logs: string[] = [];
Expand Down Expand Up @@ -504,7 +504,7 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
if (hasPage)
return;
// 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 () => {
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class Dialog extends SdkObject {
this._onHandle = onHandle;
this._defaultValue = defaultValue || '';
this._page._frameManager.dialogDidOpen(this);
this.instrumentation.onDialog(this);
}

page() {
Expand Down
13 changes: 11 additions & 2 deletions packages/playwright-core/src/server/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,25 @@ export class Download {
this._suggestedFilename = suggestedFilename;
page._browserContext._downloads.add(this);
if (suggestedFilename !== undefined)
this._page.emit(Page.Events.Download, this);
this._fireDownloadEvent();
}

page(): Page {
return this._page;
}

_filenameSuggested(suggestedFilename: string) {
assert(this._suggestedFilename === undefined);
this._suggestedFilename = suggestedFilename;
this._page.emit(Page.Events.Download, this);
this._fireDownloadEvent();
}

suggestedFilename(): string {
return this._suggestedFilename!;
}

private _fireDownloadEvent() {
this._page.instrumentation.onDownload(this._page, this);
this._page.emit(Page.Events.Download, this);
}
}
6 changes: 6 additions & 0 deletions packages/playwright-core/src/server/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export type Attribution = {
};

import type { CallMetadata } from '@protocol/callMetadata';
import type { Dialog } from './dialog';
import type { Download } from './download';
export type { CallMetadata } from '@protocol/callMetadata';

export class SdkObject extends EventEmitter {
Expand Down Expand Up @@ -62,6 +64,8 @@ export interface Instrumentation {
onPageClose(page: Page): void;
onBrowserOpen(browser: Browser): void;
onBrowserClose(browser: Browser): void;
onDialog(dialog: Dialog): void;
onDownload(page: Page, download: Download): void;
}

export interface InstrumentationListener {
Expand All @@ -73,6 +77,8 @@ export interface InstrumentationListener {
onPageClose?(page: Page): void;
onBrowserOpen?(browser: Browser): void;
onBrowserClose?(browser: Browser): void;
onDialog?(dialog: Dialog): void;
onDownload?(page: Page, download: Download): void;
}

export function createInstrumentation(): Instrumentation {
Expand Down
6 changes: 4 additions & 2 deletions packages/playwright-core/src/server/recorder/recorderApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
}).toString(), { isFunction: true }, sources).catch(() => {});

// Testing harness for runCLI mode.
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length)
(process as any)._didSetSourcesForTest(sources[0].text);
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) {
if ((process as any)._didSetSourcesForTest(sources[0].text))
this.close();
}
}

async setSelector(selector: string, userGesture?: boolean): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,77 +21,97 @@ import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFro
import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer';
import type { TraceViewerServerOptions } from '../trace/viewer/traceViewer';
import type { BrowserContext } from '../browserContext';
import { gracefullyProcessExitDoNotHang } from '../../utils/processLauncher';
import type { Transport } from '../../utils/httpServer';
import type { HttpServer, Transport } from '../../utils/httpServer';
import type { Page } from '../page';
import { ManualPromise } from '../../utils/manualPromise';

export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp {
readonly wsEndpointForTest: string | undefined;
private _recorder: IRecorder;
private _transport: Transport;
private _transport: RecorderTransport;
private _tracePage: Page;
private _traceServer: HttpServer;

static factory(context: BrowserContext): IRecorderAppFactory {
return async (recorder: IRecorder) => {
const transport = new RecorderTransport();
const trace = path.join(context._browser.options.tracesDir, 'trace');
const wsEndpointForTest = await openApp(trace, { transport, headless: !context._browser.options.headful });
return new RecorderInTraceViewer(context, recorder, transport, wsEndpointForTest);
const { wsEndpointForTest, tracePage, traceServer } = await openApp(trace, { transport, headless: !context._browser.options.headful });
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();
this._recorder = recorder;
this._transport = transport;
this._tracePage = tracePage;
this._traceServer = traceServer;
this.wsEndpointForTest = wsEndpointForTest;
this._tracePage.once('close', () => {
this.close();
});
}

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> {
this._transport.sendEvent?.('setPaused', { paused });
this._transport.deliverEvent('setPaused', { paused });
}

async setMode(mode: Mode): Promise<void> {
this._transport.sendEvent?.('setMode', { mode });
this._transport.deliverEvent('setMode', { mode });
}

async setFile(file: string): Promise<void> {
this._transport.sendEvent?.('setFileIfNeeded', { file });
this._transport.deliverEvent('setFileIfNeeded', { file });
}

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> {
this._transport.sendEvent?.('updateCallLogs', { callLogs });
this._transport.deliverEvent('updateCallLogs', { callLogs });
}

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> {
const server = await startTraceViewerServer(options);
await installRootRedirect(server, [trace], { ...options, webApp: 'recorder.html' });
const page = await openTraceViewerApp(server.urlPrefix('precise'), 'chromium', options);
page.on('close', () => gracefullyProcessExitDoNotHang(0));
return page.context()._browser.options.wsEndpoint;
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<{ wsEndpointForTest: string | undefined, tracePage: Page, traceServer: HttpServer }> {
const traceServer = await startTraceViewerServer(options);
await installRootRedirect(traceServer, [trace], { ...options, webApp: 'recorder.html' });
const page = await openTraceViewerApp(traceServer.urlPrefix('precise'), 'chromium', options);
return { wsEndpointForTest: page.context()._browser.options.wsEndpoint, tracePage: page, traceServer };
}

class RecorderTransport implements Transport {
private _connected = new ManualPromise<void>();

constructor() {
}

async dispatch(method: string, params: any) {
onconnect() {
this._connected.resolve();
}

async dispatch(method: string, params: any): Promise<any> {
}

onclose() {
}

deliverEvent(method: string, params: any) {
this._connected.then(() => this.sendEvent?.(method, params));
}

sendEvent?: (method: string, params: any) => void;
close?: () => void;
}
Loading

0 comments on commit dfb3fdf

Please sign in to comment.