diff --git a/packages/playwright/src/isomorphic/testServerConnection.ts b/packages/playwright/src/isomorphic/testServerConnection.ts index 22bdce30ff..d41376ad4f 100644 --- a/packages/playwright/src/isomorphic/testServerConnection.ts +++ b/packages/playwright/src/isomorphic/testServerConnection.ts @@ -67,12 +67,14 @@ export class TestServerConnection implements TestServerInterface, TestServerInte readonly onStdio: events.Event<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>; readonly onTestFilesChanged: events.Event<{ testFiles: string[] }>; readonly onLoadTraceRequested: events.Event<{ traceUrl: string }>; + readonly onTraceViewerEvent: events.Event<{ method: string; params: any; }>; private _onCloseEmitter = new events.EventEmitter(); private _onReportEmitter = new events.EventEmitter(); private _onStdioEmitter = new events.EventEmitter<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>(); private _onTestFilesChangedEmitter = new events.EventEmitter<{ testFiles: string[] }>(); private _onLoadTraceRequestedEmitter = new events.EventEmitter<{ traceUrl: string }>(); + private _onTraceViewerEventEmitter = new events.EventEmitter<{ method: string; params: any }>(); private _lastId = 0; private _transport: TestServerTransport; @@ -86,6 +88,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this.onStdio = this._onStdioEmitter.event; this.onTestFilesChanged = this._onTestFilesChangedEmitter.event; this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event; + this.onTraceViewerEvent = this._onTraceViewerEventEmitter.event; this._transport = transport; this._transport.onmessage(data => { @@ -146,6 +149,8 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this._onTestFilesChangedEmitter.fire(params); else if (method === 'loadTraceRequested') this._onLoadTraceRequestedEmitter.fire(params); + else if (method === 'traceViewerEvent') + this._onTraceViewerEventEmitter.fire(params); } async initialize(params: Parameters[0]): ReturnType { @@ -236,6 +241,22 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this._sendMessageNoReply('stopTests', params); } + async openTraceViewer(params: Parameters[0]): ReturnType { + await this._sendMessage('openTraceViewer', params); + } + + async closeTraceViewer(params: Parameters[0]): ReturnType { + await this._sendMessage('closeTraceViewer', params); + } + + async dispatchTraceViewerEvent(params: Parameters[0]): ReturnType { + await this._sendMessage('dispatchTraceViewerEvent', params); + } + + dispatchTraceViewerEventNoReply(params: Parameters[0]) { + this._sendMessageNoReply('dispatchTraceViewerEvent', params); + } + async closeGracefully(params: Parameters[0]): ReturnType { await this._sendMessage('closeGracefully', params); } diff --git a/packages/playwright/src/isomorphic/testServerInterface.ts b/packages/playwright/src/isomorphic/testServerInterface.ts index 22cb9e35ef..ba6ddbcd83 100644 --- a/packages/playwright/src/isomorphic/testServerInterface.ts +++ b/packages/playwright/src/isomorphic/testServerInterface.ts @@ -21,6 +21,7 @@ import type { JsonEvent } from './teleReceiver'; // -- Reuse boundary -- Everything below this line is reused in the vscode extension. export type ReportEntry = JsonEvent; +export type TraceViewerEvent = JsonEvent; export interface TestServerInterface { initialize(params: { @@ -114,6 +115,12 @@ export interface TestServerInterface { stopTests(params: {}): Promise; + openTraceViewer(params: { traceViewerURL: string; openInBrowser?: boolean }): Promise; + + closeTraceViewer(params: {}): Promise; + + dispatchTraceViewerEvent(params: { method: string; params: any; }): Promise; + closeGracefully(params: {}): Promise; } @@ -122,6 +129,7 @@ export interface TestServerInterfaceEvents { onStdio: Event<{ type: 'stdout' | 'stderr', text?: string, buffer?: string }>; onTestFilesChanged: Event<{ testFiles: string[] }>; onLoadTraceRequested: Event<{ traceUrl: string }>; + onTraceViewerEvent: Event<{ method: string; params: any; }>; } export interface TestServerInterfaceEventEmitters { @@ -129,4 +137,5 @@ export interface TestServerInterfaceEventEmitters { dispatchEvent(event: 'stdio', params: { type: 'stdout' | 'stderr', text?: string, buffer?: string }): void; dispatchEvent(event: 'testFilesChanged', params: { testFiles: string[] }): void; dispatchEvent(event: 'loadTraceRequested', params: { traceUrl: string }): void; + dispatchEvent(event: 'traceViewerEvent', params: { method: string; params: any; }): void; } diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 5d67385dc5..74f712df4f 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -16,7 +16,7 @@ import fs from 'fs'; import path from 'path'; -import { installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, startTraceViewerServer } from 'playwright-core/lib/server'; +import { installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, serverSideCallMetadata, startTraceViewerServer } from 'playwright-core/lib/server'; import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils'; import type { Transport, HttpServer } from 'playwright-core/lib/utils'; import type * as reporterTypes from '../../types/testReporter'; @@ -38,6 +38,7 @@ import { serializeError } from '../util'; import { baseFullConfig } from '../isomorphic/teleReceiver'; import { InternalReporter } from '../reporters/internalReporter'; import type { ReporterV2 } from '../reporters/reporterV2'; +import type { Page } from 'playwright-core/lib/server/page'; const originalStdoutWrite = process.stdout.write; const originalStderrWrite = process.stderr.write; @@ -80,6 +81,7 @@ export class TestServerDispatcher implements TestServerInterface { private _watchTestDirs = false; private _closeOnDisconnect = false; private _populateDependenciesOnList = false; + private _traceViewerPagePromise: Promise | undefined; constructor(configLocation: ConfigLocation) { this._configLocation = configLocation; @@ -390,6 +392,42 @@ export class TestServerDispatcher implements TestServerInterface { } } + async openTraceViewer(params: { traceViewerURL: string; openInBrowser?: boolean }): Promise { + if (this._traceViewerPagePromise) + throw new Error(`Trace Viewer already open`); + + if (params.openInBrowser) { + await openTraceInBrowser(params.traceViewerURL); + } else { + this._traceViewerPagePromise = openTraceViewerApp(params.traceViewerURL, 'chromium', { + headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== '1', + persistentContextOptions: { + handleSIGINT: false, + }, + }); + const page = await this._traceViewerPagePromise; + page.on('close', () => { + this._traceViewerPagePromise = undefined; + this._dispatchEvent('traceViewerEvent', { method: 'didClose', params: {} }); + }); + } + } + + async closeTraceViewer(): Promise { + if (!this._traceViewerPagePromise) + return; + const page = await this._traceViewerPagePromise; + await page.close(serverSideCallMetadata()); + this._traceViewerPagePromise = undefined; + } + + async dispatchTraceViewerEvent(params: { method: string; params: any; }): Promise { + if (params.method === 'loadTraceRequested') + this._dispatchEvent('loadTraceRequested', params.params); + else + this._dispatchEvent('traceViewerEvent', params); + } + async closeGracefully() { gracefullyProcessExitDoNotHang(0); }