Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: prepare to reuse test server from ui mode (6) #30008

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[] }>;
pavelfeldman marked this conversation as resolved.
Show resolved Hide resolved

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>;
pavelfeldman marked this conversation as resolved.
Show resolved Hide resolved
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
Loading