Skip to content

Commit

Permalink
chore: prepare to reuse test server from ui mode (6) (#30008)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Mar 20, 2024
1 parent 2bd3e10 commit 48ccc9c
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 62 deletions.
27 changes: 7 additions & 20 deletions packages/playwright/src/isomorphic/teleReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ export class TeleReporterReceiver {
private _reporter: Partial<ReporterV2>;
private _tests = new Map<string, TeleTestCase>();
private _rootDir!: string;
private _listOnly = false;
private _config!: reporterTypes.FullConfig;

constructor(reporter: Partial<ReporterV2>, options: TeleReporterReceiverOptions) {
Expand All @@ -144,11 +143,16 @@ export class TeleReporterReceiver {
this._reporter = reporter;
}

dispatch(mode: 'list' | 'test', message: JsonEvent): Promise<void> | void {
reset() {
this._rootSuite.suites = [];
this._rootSuite.tests = [];
this._tests.clear();
}

dispatch(message: JsonEvent): Promise<void> | void {
const { method, params } = message;
if (method === 'onConfigure') {
this._onConfigure(params.config);
this._listOnly = mode === 'list';
return;
}
if (method === 'onProject') {
Expand Down Expand Up @@ -205,23 +209,6 @@ export class TeleReporterReceiver {
// Always update project in watch mode.
projectSuite._project = this._parseProject(project);
this._mergeSuitesInto(project.suites, projectSuite);

// Remove deleted tests when listing. Empty suites will be auto-filtered
// in the UI layer.
if (this._listOnly) {
const testIds = new Set<string>();
const collectIds = (suite: JsonSuite) => {
suite.tests.map(t => t.testId).forEach(testId => testIds.add(testId));
suite.suites.forEach(collectIds);
};
project.suites.forEach(collectIds);

const filterTests = (suite: TeleSuite) => {
suite.tests = suite.tests.filter(t => testIds.has(t.id));
suite.suites.forEach(filterTests);
};
filterTests(projectSuite);
}
}

private _onBegin() {
Expand Down
24 changes: 10 additions & 14 deletions packages/playwright/src/isomorphic/testServerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ import * as events from './events';

export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents {
readonly onClose: events.Event<void>;
readonly onListReport: events.Event<any>;
readonly onTestReport: events.Event<any>;
readonly onReport: events.Event<any>;
readonly onStdio: events.Event<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>;
readonly onListChanged: events.Event<void>;
readonly onTestFilesChanged: events.Event<{ testFiles: string[] }>;
readonly onLoadTraceRequested: events.Event<{ traceUrl: string }>;

private _onCloseEmitter = new events.EventEmitter<void>();
private _onListReportEmitter = new events.EventEmitter<any>();
private _onTestReportEmitter = new events.EventEmitter<any>();
private _onReportEmitter = new events.EventEmitter<any>();
private _onStdioEmitter = new events.EventEmitter<{ type: 'stderr' | 'stdout'; text?: string | undefined; buffer?: string | undefined; }>();
private _onListChangedEmitter = new events.EventEmitter<void>();
private _onTestFilesChangedEmitter = new events.EventEmitter<{ testFiles: string[] }>();
Expand All @@ -42,8 +40,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte

constructor(wsURL: string) {
this.onClose = this._onCloseEmitter.event;
this.onListReport = this._onListReportEmitter.event;
this.onTestReport = this._onTestReportEmitter.event;
this.onReport = this._onReportEmitter.event;
this.onStdio = this._onStdioEmitter.event;
this.onListChanged = this._onListChangedEmitter.event;
this.onTestFilesChanged = this._onTestFilesChangedEmitter.event;
Expand Down Expand Up @@ -94,10 +91,8 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
}

private _dispatchEvent(method: string, params?: any) {
if (method === 'listReport')
this._onListReportEmitter.fire(params);
else if (method === 'testReport')
this._onTestReportEmitter.fire(params);
if (method === 'report')
this._onReportEmitter.fire(params);
else if (method === 'stdio')
this._onStdioEmitter.fire(params);
else if (method === 'listChanged')
Expand Down Expand Up @@ -142,9 +137,10 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
return await this._sendMessage('listFiles');
}

async listTests(params: { reporter?: string | undefined; fileNames?: string[] | undefined; }): Promise<void> {
await this._sendMessage('listTests', params);
async listTests(params: { reporter?: string | undefined; fileNames?: string[] | undefined; }): Promise<{ report: any[] }> {
return await this._sendMessage('listTests', params);
}

async runTests(params: { reporter?: string | undefined; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }): Promise<void> {
await this._sendMessage('runTests', params);
}
Expand All @@ -153,8 +149,8 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
return await this._sendMessage('findRelatedTestFiles', params);
}

async stop(): Promise<void> {
await this._sendMessage('stop');
async stopTests(): Promise<void> {
await this._sendMessage('stopTests');
}

async closeGracefully(): Promise<void> {
Expand Down
13 changes: 7 additions & 6 deletions packages/playwright/src/isomorphic/testServerInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ export interface TestServerInterface {
error?: reporterTypes.TestError;
}>;

/**
* Returns list of teleReporter events.
*/
listTests(params: {
reporter?: string;
fileNames?: string[];
}): Promise<void>;
}): Promise<{ report: any[] }>;

runTests(params: {
reporter?: string;
Expand All @@ -69,15 +72,14 @@ export interface TestServerInterface {
files: string[];
}): Promise<{ testFiles: string[]; errors?: reporterTypes.TestError[]; }>;

stop(): Promise<void>;
stopTests(): Promise<void>;

closeGracefully(): Promise<void>;
}

export interface TestServerInterfaceEvents {
onClose: Event<void>;
onListReport: Event<any>;
onTestReport: Event<any>;
onReport: Event<any>;
onStdio: Event<{ type: 'stdout' | 'stderr', text?: string, buffer?: string }>;
onListChanged: Event<void>;
onTestFilesChanged: Event<{ testFiles: string[] }>;
Expand All @@ -86,8 +88,7 @@ export interface TestServerInterfaceEvents {

export interface TestServerInterfaceEventEmitters {
dispatchEvent(event: 'close', params: {}): void;
dispatchEvent(event: 'listReport', params: any): void;
dispatchEvent(event: 'testReport', params: any): void;
dispatchEvent(event: 'report', params: any): void;
dispatchEvent(event: 'stdio', params: { type: 'stdout' | 'stderr', text?: string, buffer?: string }): void;
dispatchEvent(event: 'listChanged', params: {}): void;
dispatchEvent(event: 'testFilesChanged', params: { testFiles: string[] }): void;
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/reporters/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function createMergedReport(config: FullConfigInternal, dir: string
for (const event of events) {
if (event.method === 'onEnd')
printStatus(`building final report`);
await receiver.dispatch('test', event);
await receiver.dispatch(event);
if (event.method === 'onEnd')
printStatus(`finished building report`);
}
Expand Down
1 change: 0 additions & 1 deletion packages/playwright/src/runner/reporters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ function reporterOptions(config: FullConfigInternal, mode: 'list' | 'test' | 'ui
return {
configDir: config.configDir,
_send: send,
_mode: mode,
};
}

Expand Down
39 changes: 34 additions & 5 deletions packages/playwright/src/runner/testServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import fs from 'fs';
import path from 'path';
import { registry, startTraceViewerServer } from 'playwright-core/lib/server';
import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils';
import type { Transport, HttpServer } from 'playwright-core/lib/utils';
Expand Down Expand Up @@ -172,12 +174,17 @@ class TestServerDispatcher implements TestServerInterface {
}

async listTests(params: { reporter?: string; fileNames: string[]; }) {
this._queue = this._queue.then(() => this._innerListTests(params)).catch(printInternalError);
let report: any[] = [];
this._queue = this._queue.then(async () => {
report = await this._innerListTests(params);
}).catch(printInternalError);
await this._queue;
return { report };
}

private async _innerListTests(params: { reporter?: string; fileNames?: string[]; }) {
const wireReporter = await createReporterForTestServer(this._config, params.reporter || require.resolve('./uiModeReporter'), 'list', e => this._dispatchEvent('listReport', e));
const report: any[] = [];
const wireReporter = await createReporterForTestServer(this._config, params.reporter || require.resolve('./uiModeReporter'), 'list', e => report.push(e));
const reporter = new InternalReporter(wireReporter);
this._config.cliArgs = params.fileNames || [];
this._config.cliListOnly = true;
Expand All @@ -195,7 +202,15 @@ class TestServerDispatcher implements TestServerInterface {
projectDirs.add(p.project.testDir);
projectOutputs.add(p.project.outputDir);
}

const result = await resolveCtDirs(this._config);
if (result) {
projectDirs.add(result.templateDir);
projectOutputs.add(result.outDir);
}

this._globalWatcher.update([...projectDirs], [...projectOutputs], false);
return report;
}

async runTests(params: { reporter?: string; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }) {
Expand All @@ -204,7 +219,7 @@ class TestServerDispatcher implements TestServerInterface {
}

private async _innerRunTests(params: { reporter?: string; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }) {
await this.stop();
await this.stopTests();
const { testIds, projects, locations, grep } = params;

const testIdSet = testIds ? new Set<string>(testIds) : null;
Expand All @@ -215,7 +230,7 @@ class TestServerDispatcher implements TestServerInterface {
this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id);

const reporters = await createReporters(this._config, 'ui');
reporters.push(await createReporterForTestServer(this._config, params.reporter || require.resolve('./uiModeReporter'), 'list', e => this._dispatchEvent('testReport', e)));
reporters.push(await createReporterForTestServer(this._config, params.reporter || require.resolve('./uiModeReporter'), 'list', e => this._dispatchEvent('report', e)));
const reporter = new InternalReporter(new Multiplexer(reporters));
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
const testRun = new TestRun(this._config, reporter);
Expand Down Expand Up @@ -246,7 +261,7 @@ class TestServerDispatcher implements TestServerInterface {
return runner.findRelatedTestFiles('out-of-process', params.files);
}

async stop() {
async stopTests() {
this._testRun?.stop?.resolve();
await this._testRun?.run;
}
Expand Down Expand Up @@ -306,3 +321,17 @@ function printInternalError(e: Error) {
// eslint-disable-next-line no-console
console.error('Internal error:', e);
}

// TODO: remove CT dependency.
export async function resolveCtDirs(config: FullConfigInternal) {
const use = config.config.projects[0].use as any;
const relativeTemplateDir = use.ctTemplateDir || 'playwright';
const templateDir = await fs.promises.realpath(path.normalize(path.join(config.configDir, relativeTemplateDir))).catch(() => undefined);
if (!templateDir)
return null;
const outDir = use.ctCacheDir ? path.resolve(config.configDir, use.ctCacheDir) : path.resolve(templateDir, '.cache');
return {
outDir,
templateDir
};
}
13 changes: 9 additions & 4 deletions packages/trace-viewer/src/ui/teleSuiteUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,16 @@ export class TeleSuiteUpdater {
};
}

dispatch(mode: 'test' | 'list', message: any) {
processListReport(report: any[]) {
this._receiver.reset();
for (const message of report)
this._receiver.dispatch(message);
}

processTestReport(message: any) {
// The order of receiver dispatches matters here, we want to assign `lastRunTestCount`
// before we use it.
if (mode === 'test')
this._lastRunReceiver?.dispatch('test', message)?.catch(() => {});
this._receiver.dispatch(mode, message)?.catch(() => {});
this._lastRunReceiver?.dispatch(message)?.catch(() => {});
this._receiver.dispatch(message)?.catch(() => {});
}
}
20 changes: 9 additions & 11 deletions packages/trace-viewer/src/ui/uiModeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export const UIModeView: React.FC<{}> = ({
const onShortcutEvent = (e: KeyboardEvent) => {
if (e.code === 'F6') {
e.preventDefault();
testServerConnection?.stop().catch(() => {});
testServerConnection?.stopTests().catch(() => {});
} else if (e.code === 'F5') {
e.preventDefault();
reloadTests();
Expand Down Expand Up @@ -278,7 +278,7 @@ export const UIModeView: React.FC<{}> = ({
<div>Running {progress.passed}/{runningState.testIds.size} passed ({(progress.passed / runningState.testIds.size) * 100 | 0}%)</div>
</div>}
<ToolbarButton icon='play' title='Run all' onClick={() => runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => testServerConnection?.stop()} disabled={!isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => testServerConnection?.stopTests()} disabled={!isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='eye' title='Watch all' toggled={watchAll} onClick={() => {
setWatchedTreeIds({ value: new Set() });
setWatchAll(!watchAll);
Expand Down Expand Up @@ -648,12 +648,14 @@ const refreshRootSuite = async (testServerConnection: TestServerConnection): Pro
},
pathSeparator,
});
return testServerConnection.listTests({});
const { report } = await testServerConnection.listTests({});
teleSuiteUpdater?.processListReport(report);
};

const wireConnectionListeners = (testServerConnection: TestServerConnection) => {
testServerConnection.onListChanged(() => {
testServerConnection.listTests({}).catch(() => {});
testServerConnection.onListChanged(async () => {
const { report } = await testServerConnection.listTests({});
teleSuiteUpdater?.processListReport(report);
});

testServerConnection.onTestFilesChanged(params => {
Expand All @@ -669,12 +671,8 @@ const wireConnectionListeners = (testServerConnection: TestServerConnection) =>
}
});

testServerConnection.onListReport(params => {
teleSuiteUpdater?.dispatch('list', params);
});

testServerConnection.onTestReport(params => {
teleSuiteUpdater?.dispatch('test', params);
testServerConnection.onReport(params => {
teleSuiteUpdater?.processTestReport(params);
});

xtermDataSource.resize = (cols, rows) => {
Expand Down

0 comments on commit 48ccc9c

Please sign in to comment.