diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index 0ba4f4ebd6..1628351a3e 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -18,8 +18,8 @@ import type { Annotation } from '../common/config'; import type { FullProject, Metadata } from '../../types/test'; import type * as reporterTypes from '../../types/testReporter'; import type { ReporterV2 } from '../reporters/reporterV2'; -import { StringInternPool } from './stringInternPool'; +export type StringIntern = (s: string) => string; export type JsonLocation = reporterTypes.Location; export type JsonError = string; export type JsonStackFrame = { file: string, line: number, column: number }; @@ -28,8 +28,6 @@ export type JsonStdIOType = 'stdout' | 'stderr'; export type JsonConfig = Pick; -export type MergeReporterConfig = Pick; - export type JsonPattern = { s?: string; r?: { source: string, flags: string }; @@ -123,27 +121,28 @@ export type JsonEvent = { params: any }; +type TeleReporterReceiverOptions = { + pathSeparator: string; + mergeProjects: boolean; + mergeTestCases: boolean; + internString?: StringIntern; + configOverrides?: Pick; +}; + export class TeleReporterReceiver { private _rootSuite: TeleSuite; - private _pathSeparator: string; + private _options: TeleReporterReceiverOptions; private _reporter: Partial; private _tests = new Map(); private _rootDir!: string; private _listOnly = false; private _clearPreviousResultsWhenTestBegins: boolean = false; - private _mergeTestCases: boolean; - private _mergeProjects: boolean; - private _reportConfig: MergeReporterConfig | undefined; private _config!: reporterTypes.FullConfig; - private _stringPool = new StringInternPool(); - constructor(pathSeparator: string, reporter: Partial, mergeProjects: boolean, mergeTestCases: boolean, reportConfig?: MergeReporterConfig) { + constructor(reporter: Partial, options: TeleReporterReceiverOptions) { this._rootSuite = new TeleSuite('', 'root'); - this._pathSeparator = pathSeparator; + this._options = options; this._reporter = reporter; - this._mergeProjects = mergeProjects; - this._mergeTestCases = mergeTestCases; - this._reportConfig = reportConfig; } dispatch(mode: 'list' | 'test', message: JsonEvent): Promise | void { @@ -202,7 +201,7 @@ export class TeleReporterReceiver { } private _onProject(project: JsonProject) { - let projectSuite = this._mergeProjects ? this._rootSuite.suites.find(suite => suite.project()!.name === project.name) : undefined; + let projectSuite = this._options.mergeProjects ? this._rootSuite.suites.find(suite => suite.project()!.name === project.name) : undefined; if (!projectSuite) { projectSuite = new TeleSuite(project.name, 'project'); this._rootSuite.suites.push(projectSuite); @@ -315,17 +314,16 @@ export class TeleReporterReceiver { private _onExit(): Promise | void { // Free up the memory from the string pool. - this._stringPool = new StringInternPool(); return this._reporter.onExit?.(); } private _parseConfig(config: JsonConfig): reporterTypes.FullConfig { const result = { ...baseFullConfig, ...config }; - if (this._reportConfig) { - result.configFile = this._reportConfig.configFile; - result.reportSlowTests = this._reportConfig.reportSlowTests; - result.quiet = this._reportConfig.quiet; - result.reporter = [...this._reportConfig.reporter]; + if (this._options.configOverrides) { + result.configFile = this._options.configOverrides.configFile; + result.reportSlowTests = this._options.configOverrides.reportSlowTests; + result.quiet = this._options.configOverrides.quiet; + result.reporter = [...this._options.configOverrides.reporter]; } return result; } @@ -375,7 +373,7 @@ export class TeleReporterReceiver { private _mergeTestsInto(jsonTests: JsonTestCase[], parent: TeleSuite) { for (const jsonTest of jsonTests) { - let targetTest = this._mergeTestCases ? parent.tests.find(s => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : undefined; + let targetTest = this._options.mergeTestCases ? parent.tests.find(s => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : undefined; if (!targetTest) { targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location), jsonTest.repeatEachIndex); targetTest.parent = parent; @@ -410,9 +408,12 @@ export class TeleReporterReceiver { private _absolutePath(relativePath?: string): string | undefined { if (relativePath === undefined) return; - return this._stringPool.internString(this._rootDir + this._pathSeparator + relativePath); + return this._internString(this._rootDir + this._options.pathSeparator + relativePath); } + private _internString(s: string): string { + return this._options.internString ? this._options.internString(s) : s; + } } export class TeleSuite { diff --git a/packages/playwright/src/reporters/merge.ts b/packages/playwright/src/reporters/merge.ts index 6a38396cbb..4e4d02d697 100644 --- a/packages/playwright/src/reporters/merge.ts +++ b/packages/playwright/src/reporters/merge.ts @@ -51,9 +51,15 @@ export async function createMergedReport(config: FullConfigInternal, dir: string if (shardFiles.length === 0) throw new Error(`No report files found in ${dir}`); const eventData = await mergeEvents(dir, shardFiles, stringPool, printStatus, rootDirOverride); - // If expicit config is provided, use platform path separator, otherwise use the one from the report (if any). - const pathSep = rootDirOverride ? path.sep : (eventData.pathSeparatorFromMetadata ?? path.sep); - const receiver = new TeleReporterReceiver(pathSep, multiplexer, false, false, config.config); + // 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), + configOverrides: config.config, + }); printStatus(`processing test events`); const dispatchEvents = async (events: JsonEvent[]) => { diff --git a/packages/playwright/src/reporters/teleEmitter.ts b/packages/playwright/src/reporters/teleEmitter.ts index 96178bedf8..e06963663e 100644 --- a/packages/playwright/src/reporters/teleEmitter.ts +++ b/packages/playwright/src/reporters/teleEmitter.ts @@ -16,17 +16,17 @@ import path from 'path'; import { createGuid } from 'playwright-core/lib/utils'; -import type { FullConfig, FullResult, Location, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter'; -import type { JsonAttachment, JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver'; +import type * as reporterTypes from '../../types/testReporter'; +import type * as teleReceiver from '../isomorphic/teleReceiver'; import { serializeRegexPatterns } from '../isomorphic/teleReceiver'; import type { ReporterV2 } from './reporterV2'; export class TeleReporterEmitter implements ReporterV2 { - private _messageSink: (message: JsonEvent) => void; + private _messageSink: (message: teleReceiver.JsonEvent) => void; private _rootDir!: string; private _skipBuffers: boolean; - constructor(messageSink: (message: JsonEvent) => void, skipBuffers: boolean) { + constructor(messageSink: (message: teleReceiver.JsonEvent) => void, skipBuffers: boolean) { this._messageSink = messageSink; this._skipBuffers = skipBuffers; } @@ -35,19 +35,19 @@ export class TeleReporterEmitter implements ReporterV2 { return 'v2'; } - onConfigure(config: FullConfig) { + onConfigure(config: reporterTypes.FullConfig) { this._rootDir = config.rootDir; this._messageSink({ method: 'onConfigure', params: { config: this._serializeConfig(config) } }); } - onBegin(suite: Suite) { + onBegin(suite: reporterTypes.Suite) { const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite)); for (const project of projects) this._messageSink({ method: 'onProject', params: { project } }); this._messageSink({ method: 'onBegin', params: undefined }); } - onTestBegin(test: TestCase, result: TestResult): void { + onTestBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void { (result as any)[idSymbol] = createGuid(); this._messageSink({ method: 'onTestBegin', @@ -58,8 +58,8 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - onTestEnd(test: TestCase, result: TestResult): void { - const testEnd: JsonTestEnd = { + onTestEnd(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void { + const testEnd: teleReceiver.JsonTestEnd = { testId: test.id, expectedStatus: test.expectedStatus, annotations: test.annotations, @@ -74,7 +74,7 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - onStepBegin(test: TestCase, result: TestResult, step: TestStep): void { + onStepBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void { (step as any)[idSymbol] = createGuid(); this._messageSink({ method: 'onStepBegin', @@ -86,7 +86,7 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - onStepEnd(test: TestCase, result: TestResult, step: TestStep): void { + onStepEnd(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void { this._messageSink({ method: 'onStepEnd', params: { @@ -97,22 +97,22 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - onError(error: TestError): void { + onError(error: reporterTypes.TestError): void { this._messageSink({ method: 'onError', params: { error } }); } - onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult): void { + onStdOut(chunk: string | Buffer, test?: reporterTypes.TestCase, result?: reporterTypes.TestResult): void { this._onStdIO('stdout', chunk, test, result); } - onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult): void { + onStdErr(chunk: string | Buffer, test?: reporterTypes.TestCase, result?: reporterTypes.TestResult): void { this._onStdIO('stderr', chunk, test, result); } - private _onStdIO(type: JsonStdIOType, chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void { + private _onStdIO(type: teleReceiver.JsonStdIOType, chunk: string | Buffer, test: void | reporterTypes.TestCase, result: void | reporterTypes.TestResult): void { const isBase64 = typeof chunk !== 'string'; const data = isBase64 ? chunk.toString('base64') : chunk; this._messageSink({ @@ -121,8 +121,8 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - async onEnd(result: FullResult) { - const resultPayload: JsonFullResult = { + async onEnd(result: reporterTypes.FullResult) { + const resultPayload: teleReceiver.JsonFullResult = { status: result.status, startTime: result.startTime.getTime(), duration: result.duration, @@ -142,7 +142,7 @@ export class TeleReporterEmitter implements ReporterV2 { return false; } - private _serializeConfig(config: FullConfig): JsonConfig { + private _serializeConfig(config: reporterTypes.FullConfig): teleReceiver.JsonConfig { return { configFile: this._relativePath(config.configFile), globalTimeout: config.globalTimeout, @@ -154,9 +154,9 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - private _serializeProject(suite: Suite): JsonProject { + private _serializeProject(suite: reporterTypes.Suite): teleReceiver.JsonProject { const project = suite.project()!; - const report: JsonProject = { + const report: teleReceiver.JsonProject = { metadata: project.metadata, name: project.name, outputDir: this._relativePath(project.outputDir), @@ -178,7 +178,7 @@ export class TeleReporterEmitter implements ReporterV2 { return report; } - private _serializeSuite(suite: Suite): JsonSuite { + private _serializeSuite(suite: reporterTypes.Suite): teleReceiver.JsonSuite { const result = { title: suite.title, location: this._relativeLocation(suite.location), @@ -188,7 +188,7 @@ export class TeleReporterEmitter implements ReporterV2 { return result; } - private _serializeTest(test: TestCase): JsonTestCase { + private _serializeTest(test: reporterTypes.TestCase): teleReceiver.JsonTestCase { return { testId: test.id, title: test.title, @@ -199,7 +199,7 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - private _serializeResultStart(result: TestResult): JsonTestResultStart { + private _serializeResultStart(result: reporterTypes.TestResult): teleReceiver.JsonTestResultStart { return { id: (result as any)[idSymbol], retry: result.retry, @@ -209,7 +209,7 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - private _serializeResultEnd(result: TestResult): JsonTestResultEnd { + private _serializeResultEnd(result: reporterTypes.TestResult): teleReceiver.JsonTestResultEnd { return { id: (result as any)[idSymbol], duration: result.duration, @@ -219,7 +219,7 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - _serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] { + _serializeAttachments(attachments: reporterTypes.TestResult['attachments']): teleReceiver.JsonAttachment[] { return attachments.map(a => { return { ...a, @@ -229,7 +229,7 @@ export class TeleReporterEmitter implements ReporterV2 { }); } - private _serializeStepStart(step: TestStep): JsonTestStepStart { + private _serializeStepStart(step: reporterTypes.TestStep): teleReceiver.JsonTestStepStart { return { id: (step as any)[idSymbol], parentStepId: (step.parent as any)?.[idSymbol], @@ -240,7 +240,7 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - private _serializeStepEnd(step: TestStep): JsonTestStepEnd { + private _serializeStepEnd(step: reporterTypes.TestStep): teleReceiver.JsonTestStepEnd { return { id: (step as any)[idSymbol], duration: step.duration, @@ -248,9 +248,9 @@ export class TeleReporterEmitter implements ReporterV2 { }; } - private _relativeLocation(location: Location): Location; - private _relativeLocation(location?: Location): Location | undefined; - private _relativeLocation(location: Location | undefined): Location | undefined { + private _relativeLocation(location: reporterTypes.Location): reporterTypes.Location; + private _relativeLocation(location?: reporterTypes.Location): reporterTypes.Location | undefined; + private _relativeLocation(location: reporterTypes.Location | undefined): reporterTypes.Location | undefined { if (!location) return location; return { diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index acdcb141aa..945294fa94 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -640,7 +640,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { skipped: 0, }; let config: FullConfig; - receiver = new TeleReporterReceiver(pathSeparator, { + receiver = new TeleReporterReceiver({ version: () => 'v2', onConfigure: (c: FullConfig) => { @@ -649,12 +649,16 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { // run one test, we still get many tests via rootSuite.allTests().length. // To work around that, have a dedicated per-run receiver that will only have // suite for a single test run, and hence will have correct total. - lastRunReceiver = new TeleReporterReceiver(pathSeparator, { + lastRunReceiver = new TeleReporterReceiver({ onBegin: (suite: Suite) => { lastRunTestCount = suite.allTests().length; lastRunReceiver = undefined; } - }, true, false); + }, { + pathSeparator, + mergeProjects: true, + mergeTestCases: false + }); }, onBegin: (suite: Suite) => { @@ -700,7 +704,11 @@ const refreshRootSuite = (eraseResults: boolean): Promise => { onExit: () => {}, onStepBegin: () => {}, onStepEnd: () => {}, - }, true, true); + }, { + pathSeparator, + mergeProjects: true, + mergeTestCases: true, + }); receiver._setClearPreviousResultsWhenTestBegins(); return sendMessage('list', {}); };