fix: patch project names and ids when merging reports (#23295)
Project name is used in testId calculation, so patching it in the reporter is too late. Instead we now save project suffix to the blob report file and patch all project and test ids as well as project names in the report merger
This commit is contained in:
Родитель
6a2d07401e
Коммит
0ef8832f56
|
@ -21,6 +21,7 @@ import { mime } from 'playwright-core/lib/utilsBundle';
|
|||
import { Readable } from 'stream';
|
||||
import type { FullConfig, FullResult, TestResult } from '../../types/testReporter';
|
||||
import type { Suite } from '../common/test';
|
||||
import type { JsonEvent } from '../isomorphic/teleReceiver';
|
||||
import { TeleReporterEmitter } from './teleEmitter';
|
||||
|
||||
|
||||
|
@ -29,8 +30,12 @@ type BlobReporterOptions = {
|
|||
outputDir?: string;
|
||||
};
|
||||
|
||||
export type BlobReportMetadata = {
|
||||
projectSuffix?: string;
|
||||
};
|
||||
|
||||
export class BlobReporter extends TeleReporterEmitter {
|
||||
private _messages: any[] = [];
|
||||
private _messages: JsonEvent[] = [];
|
||||
private _options: BlobReporterOptions;
|
||||
private _salt: string;
|
||||
private _copyFilePromises = new Set<Promise<void>>();
|
||||
|
@ -42,6 +47,13 @@ export class BlobReporter extends TeleReporterEmitter {
|
|||
super(message => this._messages.push(message));
|
||||
this._options = options;
|
||||
this._salt = createGuid();
|
||||
|
||||
this._messages.push({
|
||||
method: 'onBlobReportMetadata',
|
||||
params: {
|
||||
projectSuffix: process.env.PWTEST_BLOB_SUFFIX,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
|
@ -66,11 +78,6 @@ export class BlobReporter extends TeleReporterEmitter {
|
|||
]);
|
||||
}
|
||||
|
||||
override _serializeProjectName(name: string): string {
|
||||
const suffix = process.env.PWTEST_BLOB_SUFFIX;
|
||||
return name + (suffix ? suffix : '');
|
||||
}
|
||||
|
||||
override _serializeAttachments(attachments: TestResult['attachments']): TestResult['attachments'] {
|
||||
return attachments.map(attachment => {
|
||||
if (!attachment.path || !fs.statSync(attachment.path).isFile())
|
||||
|
|
|
@ -19,7 +19,8 @@ import path from 'path';
|
|||
import type { ReporterDescription } from '../../types/test';
|
||||
import type { FullResult } from '../../types/testReporter';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { TeleReporterReceiver, type JsonEvent, type JsonProject, type JsonSuite, type JsonTestResultEnd, type JsonConfig } from '../isomorphic/teleReceiver';
|
||||
import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver';
|
||||
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
||||
import { createReporters } from '../runner/reporters';
|
||||
import { Multiplexer } from './multiplexer';
|
||||
|
||||
|
@ -65,6 +66,8 @@ async function mergeEvents(dir: string, shardReportFiles: string[]) {
|
|||
beginEvents.push(event);
|
||||
else if (event.method === 'onEnd')
|
||||
endEvents.push(event);
|
||||
else if (event.method === 'onBlobReportMetadata')
|
||||
new ProjectNamePatcher(event.params.projectSuffix).patchEvents(parsedEvents);
|
||||
else
|
||||
events.push(event);
|
||||
}
|
||||
|
@ -153,3 +156,61 @@ async function sortedShardFiles(dir: string) {
|
|||
const files = await fs.promises.readdir(dir);
|
||||
return files.filter(file => file.endsWith('.jsonl')).sort();
|
||||
}
|
||||
|
||||
class ProjectNamePatcher {
|
||||
constructor(private _projectNameSuffix: string) {
|
||||
}
|
||||
|
||||
patchEvents(events: JsonEvent[]) {
|
||||
if (!this._projectNameSuffix)
|
||||
return;
|
||||
for (const event of events) {
|
||||
const { method, params } = event;
|
||||
switch (method) {
|
||||
case 'onBegin':
|
||||
this._onBegin(params.config, params.projects);
|
||||
continue;
|
||||
case 'onTestBegin':
|
||||
case 'onStepBegin':
|
||||
case 'onStepEnd':
|
||||
case 'onStdIO':
|
||||
params.testId = this._mapTestId(params.testId);
|
||||
continue;
|
||||
case 'onTestEnd':
|
||||
params.test.testId = this._mapTestId(params.test.testId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onBegin(config: JsonConfig, projects: JsonProject[]) {
|
||||
for (const project of projects)
|
||||
project.name += this._projectNameSuffix;
|
||||
this._updateProjectIds(projects);
|
||||
for (const project of projects)
|
||||
project.suites.forEach(suite => this._updateTestIds(suite));
|
||||
}
|
||||
|
||||
private _updateProjectIds(projects: JsonProject[]) {
|
||||
const usedNames = new Set<string>();
|
||||
for (const p of projects) {
|
||||
for (let i = 0; i < projects.length; ++i) {
|
||||
const candidate = p.name + (i ? i : '');
|
||||
if (usedNames.has(candidate))
|
||||
continue;
|
||||
p.id = candidate;
|
||||
usedNames.add(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateTestIds(suite: JsonSuite) {
|
||||
suite.tests.forEach(test => test.testId = this._mapTestId(test.testId));
|
||||
suite.suites.forEach(suite => this._updateTestIds(suite));
|
||||
}
|
||||
|
||||
private _mapTestId(testId: string): string {
|
||||
return testId + '-' + this._projectNameSuffix;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,32 +14,26 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { FullConfig, FullResult, Reporter, TestError, TestResult, TestStep, Location } from '../../types/testReporter';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { JsonConfig, JsonProject, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
import { FullConfigInternal } from '../common/config';
|
||||
import { createGuid } from 'playwright-core/lib/utils';
|
||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||
import path from 'path';
|
||||
import type { FullProject } from '../../types/test';
|
||||
import { createGuid } from 'playwright-core/lib/utils';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||
import { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||
|
||||
export class TeleReporterEmitter implements Reporter {
|
||||
private _messageSink: (message: any) => void;
|
||||
private _messageSink: (message: JsonEvent) => void;
|
||||
private _rootDir!: string;
|
||||
|
||||
constructor(messageSink: (message: any) => void) {
|
||||
constructor(messageSink: (message: JsonEvent) => void) {
|
||||
this._messageSink = messageSink;
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
this._rootDir = config.rootDir;
|
||||
const projects: any[] = [];
|
||||
const projectIds = this._uniqueProjectIds(config.projects);
|
||||
for (const projectSuite of suite.suites) {
|
||||
const report = this._serializeProject(projectSuite, projectIds);
|
||||
projects.push(report);
|
||||
}
|
||||
const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
|
||||
this._messageSink({ method: 'onBegin', params: { config: this._serializeConfig(config), projects } });
|
||||
}
|
||||
|
||||
|
@ -138,29 +132,12 @@ export class TeleReporterEmitter implements Reporter {
|
|||
};
|
||||
}
|
||||
|
||||
private _uniqueProjectIds(projects: FullProject[]): Map<FullProject, string> {
|
||||
const usedNames = new Set<string>();
|
||||
const result = new Map<FullProject, string>();
|
||||
for (const p of projects) {
|
||||
const name = this._serializeProjectName(p.name);
|
||||
for (let i = 0; i < projects.length; ++i) {
|
||||
const candidate = name + (i ? i : '');
|
||||
if (usedNames.has(candidate))
|
||||
continue;
|
||||
result.set(p, candidate);
|
||||
usedNames.add(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _serializeProject(suite: Suite, projectIds: Map<FullProject, string>): JsonProject {
|
||||
private _serializeProject(suite: Suite): JsonProject {
|
||||
const project = suite.project()!;
|
||||
const report: JsonProject = {
|
||||
id: projectIds.get(project)!,
|
||||
id: FullProjectInternal.from(project).id,
|
||||
metadata: project.metadata,
|
||||
name: this._serializeProjectName(project.name),
|
||||
name: project.name,
|
||||
outputDir: this._relativePath(project.outputDir),
|
||||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
|
@ -180,10 +157,6 @@ export class TeleReporterEmitter implements Reporter {
|
|||
return report;
|
||||
}
|
||||
|
||||
_serializeProjectName(name: string): string {
|
||||
return name;
|
||||
}
|
||||
|
||||
private _serializeSuite(suite: Suite): JsonSuite {
|
||||
const result = {
|
||||
type: suite._type,
|
||||
|
|
Загрузка…
Ссылка в новой задаче