chore: iterate towards tele reporter reuse in vscode (2) (#29812)

This commit is contained in:
Pavel Feldman 2024-03-04 19:52:20 -08:00 committed by GitHub
parent 5eb8fea616
commit e314b83e56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 52 additions and 58 deletions

View file

@ -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<reporterTypes.FullConfig, 'configFile' | 'quiet' | 'reportSlowTests' | 'reporter'>;
};
@ -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<string, TeleTestResult>();
_resultsMap = new Map<string, TeleTestResult>();
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<string, reporterTypes.TestStep> = new Map();
statusEx: reporterTypes.TestResult['status'] | 'scheduled' | 'running' = 'scheduled';
_stepMap: Map<string, reporterTypes.TestStep> = new Map();
private _startTime: number = 0;

View file

@ -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}`);

View file

@ -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`);

View file

@ -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,
};
});
}

View file

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

View file

@ -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[];

View file

@ -33,13 +33,13 @@ export interface TestServerInterface {
listTests(params: {
configFile: string;
locations: string[];
reporters: { file: string, event: string }[];
reporter: string;
}): Promise<void>;
test(params: {
configFile: string;
locations: string[];
reporters: { file: string, event: string }[];
reporter: string;
headed?: boolean;
oneWorker?: boolean;
trace?: 'on' | 'off';

View file

@ -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);

View file

@ -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<void> => {
lastRunReceiver = undefined;
}
}, {
pathSeparator,
mergeProjects: true,
mergeTestCases: false
mergeTestCases: false,
resolvePath: (rootDir, relativePath) => rootDir + pathSeparator + relativePath,
});
},
@ -675,17 +676,19 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
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<void> => {
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<MultiTraceModel> {
}
const pathSeparator = navigator.userAgent.toLowerCase().includes('windows') ? '\\' : '/';
const statusEx = Symbol('statusEx');