From e314b83e56817a3b0f1766851b2becf7fe488181 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 4 Mar 2024 19:52:20 -0800 Subject: [PATCH] chore: iterate towards tele reporter reuse in vscode (2) (#29812) --- .../playwright/src/isomorphic/teleReceiver.ts | 36 ++++++++----------- packages/playwright/src/reporters/blob.ts | 2 +- packages/playwright/src/reporters/merge.ts | 3 +- .../playwright/src/reporters/teleEmitter.ts | 15 +++++--- packages/playwright/src/runner/reporters.ts | 2 +- packages/playwright/src/runner/testServer.ts | 20 ++++------- .../src/runner/testServerInterface.ts | 4 +-- packages/playwright/src/runner/uiMode.ts | 4 +-- packages/trace-viewer/src/ui/uiModeView.tsx | 24 +++++++------ 9 files changed, 52 insertions(+), 58 deletions(-) diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index 8b64ce747a..db573a0f6f 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -122,10 +122,9 @@ export type JsonEvent = { }; type TeleReporterReceiverOptions = { - pathSeparator: string; mergeProjects: boolean; mergeTestCases: boolean; - internString?: StringIntern; + resolvePath: (rootDir: string, relativePath: string) => string; configOverrides?: Pick; }; @@ -242,7 +241,6 @@ export class TeleReporterReceiver { testResult.workerIndex = payload.workerIndex; testResult.parallelIndex = payload.parallelIndex; testResult.setStartTimeNumber(payload.startTime); - testResult.statusEx = 'running'; this._reporter.onTestBegin?.(test, testResult); } @@ -251,22 +249,21 @@ export class TeleReporterReceiver { test.timeout = testEndPayload.timeout; test.expectedStatus = testEndPayload.expectedStatus; test.annotations = testEndPayload.annotations; - const result = test.resultsMap.get(payload.id)!; + const result = test._resultsMap.get(payload.id)!; result.duration = payload.duration; result.status = payload.status; - result.statusEx = payload.status; result.errors = payload.errors; result.error = result.errors?.[0]; result.attachments = this._parseAttachments(payload.attachments); this._reporter.onTestEnd?.(test, result); // Free up the memory as won't see these step ids. - result.stepMap = new Map(); + result._stepMap = new Map(); } private _onStepBegin(testId: string, resultId: string, payload: JsonTestStepStart) { const test = this._tests.get(testId)!; - const result = test.resultsMap.get(resultId)!; - const parentStep = payload.parentStepId ? result.stepMap.get(payload.parentStepId) : undefined; + const result = test._resultsMap.get(resultId)!; + const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : undefined; const location = this._absoluteLocation(payload.location); const step = new TeleTestStep(payload, parentStep, location); @@ -274,14 +271,14 @@ export class TeleReporterReceiver { parentStep.steps.push(step); else result.steps.push(step); - result.stepMap.set(payload.id, step); + result._stepMap.set(payload.id, step); this._reporter.onStepBegin?.(test, result, step); } private _onStepEnd(testId: string, resultId: string, payload: JsonTestStepEnd) { const test = this._tests.get(testId)!; - const result = test.resultsMap.get(resultId)!; - const step = result.stepMap.get(payload.id)!; + const result = test._resultsMap.get(resultId)!; + const step = result._stepMap.get(payload.id)!; step.duration = payload.duration; step.error = payload.error; this._reporter.onStepEnd?.(test, result, step); @@ -294,7 +291,7 @@ export class TeleReporterReceiver { private _onStdIO(type: JsonStdIOType, testId: string | undefined, resultId: string | undefined, data: string, isBase64: boolean) { const chunk = isBase64 ? ((globalThis as any).Buffer ? Buffer.from(data, 'base64') : atob(data)) : data; const test = testId ? this._tests.get(testId) : undefined; - const result = test && resultId ? test.resultsMap.get(resultId) : undefined; + const result = test && resultId ? test._resultsMap.get(resultId) : undefined; if (type === 'stdout') { result?.stdout.push(chunk); this._reporter.onStdOut?.(chunk, test, result); @@ -407,11 +404,7 @@ export class TeleReporterReceiver { private _absolutePath(relativePath?: string): string | undefined { if (relativePath === undefined) return; - return this._internString(this._rootDir + this._options.pathSeparator + relativePath); - } - - private _internString(s: string): string { - return this._options.internString ? this._options.internString(s) : s; + return this._options.resolvePath(this._rootDir, relativePath); } } @@ -475,7 +468,7 @@ export class TeleTestCase implements reporterTypes.TestCase { repeatEachIndex = 0; id: string; - resultsMap = new Map(); + _resultsMap = new Map(); constructor(id: string, title: string, location: reporterTypes.Location, repeatEachIndex: number) { this.id = id; @@ -515,13 +508,13 @@ export class TeleTestCase implements reporterTypes.TestCase { _clearResults() { this.results = []; - this.resultsMap.clear(); + this._resultsMap.clear(); } _createTestResult(id: string): TeleTestResult { const result = new TeleTestResult(this.results.length); this.results.push(result); - this.resultsMap.set(id, result); + this._resultsMap.set(id, result); return result; } } @@ -571,8 +564,7 @@ class TeleTestResult implements reporterTypes.TestResult { errors: reporterTypes.TestResult['errors'] = []; error: reporterTypes.TestResult['error']; - stepMap: Map = new Map(); - statusEx: reporterTypes.TestResult['status'] | 'scheduled' | 'running' = 'scheduled'; + _stepMap: Map = new Map(); private _startTime: number = 0; diff --git a/packages/playwright/src/reporters/blob.ts b/packages/playwright/src/reporters/blob.ts index 03848a4c76..019fa8b7ca 100644 --- a/packages/playwright/src/reporters/blob.ts +++ b/packages/playwright/src/reporters/blob.ts @@ -50,7 +50,7 @@ export class BlobReporter extends TeleReporterEmitter { private _reportName!: string; constructor(options: BlobReporterOptions) { - super(message => this._messages.push(message), false); + super(message => this._messages.push(message)); this._options = options; if (this._options.fileName && !this._options.fileName.endsWith('.zip')) throw new Error(`Blob report file name must end with .zip extension: ${this._options.fileName}`); diff --git a/packages/playwright/src/reporters/merge.ts b/packages/playwright/src/reporters/merge.ts index 4e4d02d697..0b21182bba 100644 --- a/packages/playwright/src/reporters/merge.ts +++ b/packages/playwright/src/reporters/merge.ts @@ -54,10 +54,9 @@ export async function createMergedReport(config: FullConfigInternal, dir: string // If explicit config is provided, use platform path separator, otherwise use the one from the report (if any). const pathSeparator = rootDirOverride ? path.sep : (eventData.pathSeparatorFromMetadata ?? path.sep); const receiver = new TeleReporterReceiver(multiplexer, { - pathSeparator, mergeProjects: false, mergeTestCases: false, - internString: s => stringPool.internString(s), + resolvePath: (rootDir, relativePath) => stringPool.internString(rootDir + pathSeparator + relativePath), configOverrides: config.config, }); printStatus(`processing test events`); diff --git a/packages/playwright/src/reporters/teleEmitter.ts b/packages/playwright/src/reporters/teleEmitter.ts index e06963663e..2baef221aa 100644 --- a/packages/playwright/src/reporters/teleEmitter.ts +++ b/packages/playwright/src/reporters/teleEmitter.ts @@ -21,14 +21,19 @@ import type * as teleReceiver from '../isomorphic/teleReceiver'; import { serializeRegexPatterns } from '../isomorphic/teleReceiver'; import type { ReporterV2 } from './reporterV2'; +export type TeleReporterEmitterOptions = { + omitOutput?: boolean; + omitBuffers?: boolean; +}; + export class TeleReporterEmitter implements ReporterV2 { private _messageSink: (message: teleReceiver.JsonEvent) => void; private _rootDir!: string; - private _skipBuffers: boolean; + private _emitterOptions: TeleReporterEmitterOptions; - constructor(messageSink: (message: teleReceiver.JsonEvent) => void, skipBuffers: boolean) { + constructor(messageSink: (message: teleReceiver.JsonEvent) => void, options: TeleReporterEmitterOptions = {}) { this._messageSink = messageSink; - this._skipBuffers = skipBuffers; + this._emitterOptions = options; } version(): 'v2' { @@ -113,6 +118,8 @@ export class TeleReporterEmitter implements ReporterV2 { } private _onStdIO(type: teleReceiver.JsonStdIOType, chunk: string | Buffer, test: void | reporterTypes.TestCase, result: void | reporterTypes.TestResult): void { + if (this._emitterOptions.omitOutput) + return; const isBase64 = typeof chunk !== 'string'; const data = isBase64 ? chunk.toString('base64') : chunk; this._messageSink({ @@ -224,7 +231,7 @@ export class TeleReporterEmitter implements ReporterV2 { return { ...a, // There is no Buffer in the browser, so there is no point in sending the data there. - base64: (a.body && !this._skipBuffers) ? a.body.toString('base64') : undefined, + base64: (a.body && !this._emitterOptions.omitBuffers) ? a.body.toString('base64') : undefined, }; }); } diff --git a/packages/playwright/src/runner/reporters.ts b/packages/playwright/src/runner/reporters.ts index 3ec842f7e7..3ab0ca18c6 100644 --- a/packages/playwright/src/runner/reporters.ts +++ b/packages/playwright/src/runner/reporters.ts @@ -88,7 +88,7 @@ export async function createReporterForTestServer(config: FullConfigInternal, fi function reporterOptions(config: FullConfigInternal, mode: 'list' | 'test' | 'ui' | 'merge', send?: (message: any) => void) { return { configDir: config.configDir, - send, + _send: send, _mode: mode, }; } diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 4df7f7fd49..38c950d164 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -114,13 +114,13 @@ class Dispatcher implements TestServerInterface { async listTests(params: { configFile: string; locations: string[]; - reporters: { file: string, event: string }[]; + reporter: string; env: NodeJS.ProcessEnv; }) { const config = await this._loadConfig(params.configFile); config.cliArgs = params.locations || []; - const wireReporters = await this._wireReporters(config, 'list', params.reporters); - const reporter = new InternalReporter(new Multiplexer(wireReporters)); + const wireReporter = await createReporterForTestServer(config, params.reporter, 'list', message => this._dispatchEvent('report', message)); + const reporter = new InternalReporter(new Multiplexer([wireReporter])); const taskRunner = createTaskRunnerForList(config, reporter, 'out-of-process', { failOnLoadErrors: true }); const testRun = new TestRun(config, reporter); reporter.onConfigure(config.config); @@ -138,7 +138,7 @@ class Dispatcher implements TestServerInterface { async test(params: { configFile: string; locations: string[]; - reporters: { file: string, event: string }[]; + reporter: string; env: NodeJS.ProcessEnv; headed?: boolean; oneWorker?: boolean; @@ -169,9 +169,9 @@ class Dispatcher implements TestServerInterface { config.cliGrep = params.grep; config.cliProjectFilter = params.projects?.length ? params.projects : undefined; - const wireReporters = await this._wireReporters(config, 'test', params.reporters); + const wireReporter = await createReporterForTestServer(config, params.reporter, 'test', message => this._dispatchEvent('report', message)); const configReporters = await createReporters(config, 'test'); - const reporter = new InternalReporter(new Multiplexer([...configReporters, ...wireReporters])); + const reporter = new InternalReporter(new Multiplexer([...configReporters, wireReporter])); const taskRunner = createTaskRunnerForTestServer(config, reporter); const testRun = new TestRun(config, reporter); reporter.onConfigure(config.config); @@ -186,14 +186,6 @@ class Dispatcher implements TestServerInterface { await run; } - private async _wireReporters(config: FullConfigInternal, mode: 'test' | 'list', reporters: { file: string, event: string }[]) { - return await Promise.all(reporters.map(r => { - return createReporterForTestServer(config, r.file, mode, message => { - this._dispatchEvent(r.event, message); - }); - })); - } - async findRelatedTestFiles(params: { configFile: string; files: string[]; diff --git a/packages/playwright/src/runner/testServerInterface.ts b/packages/playwright/src/runner/testServerInterface.ts index 23c404da84..1e844d8123 100644 --- a/packages/playwright/src/runner/testServerInterface.ts +++ b/packages/playwright/src/runner/testServerInterface.ts @@ -33,13 +33,13 @@ export interface TestServerInterface { listTests(params: { configFile: string; locations: string[]; - reporters: { file: string, event: string }[]; + reporter: string; }): Promise; test(params: { configFile: string; locations: string[]; - reporters: { file: string, event: string }[]; + reporter: string; headed?: boolean; oneWorker?: boolean; trace?: 'on' | 'off'; diff --git a/packages/playwright/src/runner/uiMode.ts b/packages/playwright/src/runner/uiMode.ts index 61a5aaddce..ac6c6bf11b 100644 --- a/packages/playwright/src/runner/uiMode.ts +++ b/packages/playwright/src/runner/uiMode.ts @@ -167,7 +167,7 @@ class UIMode { } private async _listTests() { - const reporter = new InternalReporter(new TeleReporterEmitter(e => this._dispatchEvent('listReport', e), true)); + const reporter = new InternalReporter(new TeleReporterEmitter(e => this._dispatchEvent('listReport', e), { omitBuffers: true })); this._config.cliListOnly = true; this._config.testIdMatcher = undefined; const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process', { failOnLoadErrors: false }); @@ -195,7 +195,7 @@ class UIMode { this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id); const reporters = await createReporters(this._config, 'ui'); - reporters.push(new TeleReporterEmitter(e => this._dispatchEvent('testReport', e), true)); + reporters.push(new TeleReporterEmitter(e => this._dispatchEvent('testReport', e), { omitBuffers: true })); const reporter = new InternalReporter(new Multiplexer(reporters)); const taskRunner = createTaskRunnerForWatch(this._config, reporter); const testRun = new TestRun(this._config, reporter); diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 945294fa94..b0c392884f 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -22,7 +22,7 @@ import { TreeView } from '@web/components/treeView'; import type { TreeState } from '@web/components/treeView'; import { baseFullConfig, TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver'; import type { TeleTestCase } from '@testIsomorphic/teleReceiver'; -import type { FullConfig, Suite, TestCase, Location, TestError } from 'playwright/types/testReporter'; +import type { FullConfig, Suite, TestCase, Location, TestError, TestResult } from 'playwright/types/testReporter'; import { SplitView } from '@web/components/splitView'; import { idForAction, MultiTraceModel } from './modelUtil'; import type { SourceLocation } from './modelUtil'; @@ -151,7 +151,8 @@ export const UIModeView: React.FC<{}> = ({ for (const test of testModel.rootSuite?.allTests() || []) { if (testIds.has(test.id)) { (test as TeleTestCase)._clearResults(); - (test as TeleTestCase)._createTestResult('pending'); + const result = (test as TeleTestCase)._createTestResult('pending'); + (result as any)[statusEx] = 'scheduled'; } } setTestModel({ ...testModel }); @@ -655,9 +656,9 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { lastRunReceiver = undefined; } }, { - pathSeparator, mergeProjects: true, - mergeTestCases: false + mergeTestCases: false, + resolvePath: (rootDir, relativePath) => rootDir + pathSeparator + relativePath, }); }, @@ -675,17 +676,19 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { throttleUpdateRootSuite(config, rootSuite, loadErrors, progress, true); }, - onTestBegin: () => { + onTestBegin: (test: TestCase, testResult: TestResult) => { + (testResult as any)[statusEx] = 'running'; throttleUpdateRootSuite(config, rootSuite, loadErrors, progress); }, - onTestEnd: (test: TestCase) => { + onTestEnd: (test: TestCase, testResult: TestResult) => { if (test.outcome() === 'skipped') ++progress.skipped; else if (test.outcome() === 'unexpected') ++progress.failed; else ++progress.passed; + (testResult as any)[statusEx] = testResult.status; throttleUpdateRootSuite(config, rootSuite, loadErrors, progress); }, @@ -705,9 +708,9 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { onStepBegin: () => {}, onStepEnd: () => {}, }, { - pathSeparator, mergeProjects: true, mergeTestCases: true, + resolvePath: (rootDir, relativePath) => rootDir + pathSeparator + relativePath, }); receiver._setClearPreviousResultsWhenTestBegins(); return sendMessage('list', {}); @@ -906,11 +909,11 @@ function createTree(rootSuite: Suite | undefined, loadErrors: TestError[], proje parentGroup.children.push(testCaseItem); } - const result = (test as TeleTestCase).results[0]; + const result = test.results[0]; let status: 'none' | 'running' | 'scheduled' | 'passed' | 'failed' | 'skipped' = 'none'; - if (result?.statusEx === 'scheduled') + if ((result as any)?.[statusEx] === 'scheduled') status = 'scheduled'; - else if (result?.statusEx === 'running') + else if ((result as any)?.[statusEx] === 'running') status = 'running'; else if (result?.status === 'skipped') status = 'skipped'; @@ -1053,3 +1056,4 @@ async function loadSingleTraceFile(url: string): Promise { } const pathSeparator = navigator.userAgent.toLowerCase().includes('windows') ? '\\' : '/'; +const statusEx = Symbol('statusEx');