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:
Yury Semikhatsky 2023-05-25 23:02:23 -07:00 коммит произвёл GitHub
Родитель 6a2d07401e
Коммит 0ef8832f56
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 88 добавлений и 47 удалений

Просмотреть файл

@ -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,