chore: include start/endTime and duration in onEnd report callback (#26760)
Fixes https://github.com/microsoft/playwright/issues/23637
This commit is contained in:
parent
a9bc1a1707
commit
34c6197f9e
|
|
@ -113,6 +113,8 @@ Called after all tests have been run, or testing has been interrupted. Note that
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- `result` <[Object]>
|
- `result` <[Object]>
|
||||||
- `status` <[FullStatus]<"passed"|"failed"|"timedout"|"interrupted">>
|
- `status` <[FullStatus]<"passed"|"failed"|"timedout"|"interrupted">>
|
||||||
|
- `startTime` <[Date]>
|
||||||
|
- `duration` <[int]>
|
||||||
|
|
||||||
Result of the full test run.
|
Result of the full test run.
|
||||||
* `'passed'` - Everything went as expected.
|
* `'passed'` - Everything went as expected.
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
background-color: var(--color-canvas-subtle);
|
background-color: var(--color-canvas-subtle);
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
margin-top: 24px;
|
margin-top: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 38px;
|
line-height: 38px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
border-bottom-left-radius: 6px;
|
border-bottom-left-radius: 6px;
|
||||||
border-bottom-right-radius: 6px;
|
border-bottom-right-radius: 6px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chip-body-no-insets {
|
.chip-body-no-insets {
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,11 @@ export const TestFilesView: React.FC<{
|
||||||
return result;
|
return result;
|
||||||
}, [report, filter]);
|
}, [report, filter]);
|
||||||
return <>
|
return <>
|
||||||
<div className='p-2' style={{ display: 'flex' }}>
|
<div className='mt-2 mx-1' style={{ display: 'flex' }}>
|
||||||
{projectNames.length === 1 && !!projectNames[0] && <div data-testid="project-name" style={{ color: 'var(--color-fg-subtle)' }}>Project: {projectNames[0]}</div>}
|
{projectNames.length === 1 && !!projectNames[0] && <div data-testid="project-name" style={{ color: 'var(--color-fg-subtle)' }}>Project: {projectNames[0]}</div>}
|
||||||
{!filter.empty() && <div data-testid="filtered-tests-count" style={{ color: 'var(--color-fg-subtle)', padding: '0 10px' }}>Filtered: {filteredStats.total}</div>}
|
{!filter.empty() && <div data-testid="filtered-tests-count" style={{ color: 'var(--color-fg-subtle)', padding: '0 10px' }}>Filtered: {filteredStats.total}</div>}
|
||||||
<div style={{ flex: 'auto' }}></div>
|
<div style={{ flex: 'auto' }}></div>
|
||||||
|
<div data-testid="overall-time" style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{report ? new Date(report.startTime).toLocaleString() : ''}</div>
|
||||||
<div data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(filteredStats.duration)}</div>
|
<div data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(filteredStats.duration)}</div>
|
||||||
</div>
|
</div>
|
||||||
{report && filteredFiles.map(({ file, defaultExpanded }) => {
|
{report && filteredFiles.map(({ file, defaultExpanded }) => {
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,10 @@ export type Stats = {
|
||||||
flaky: number;
|
flaky: number;
|
||||||
skipped: number;
|
skipped: number;
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
duration: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilteredStats = {
|
export type FilteredStats = {
|
||||||
total: number
|
total: number,
|
||||||
duration: number,
|
duration: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -42,6 +41,8 @@ export type HTMLReport = {
|
||||||
files: TestFileSummary[];
|
files: TestFileSummary[];
|
||||||
stats: Stats;
|
stats: Stats;
|
||||||
projectNames: string[];
|
projectNames: string[];
|
||||||
|
startTime: number;
|
||||||
|
duration: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestFile = {
|
export type TestFile = {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,12 @@ export type JsonTestStepEnd = {
|
||||||
error?: TestError;
|
error?: TestError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type JsonFullResult = {
|
||||||
|
status: FullResult['status'];
|
||||||
|
startTime: number;
|
||||||
|
duration: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type JsonEvent = {
|
export type JsonEvent = {
|
||||||
method: string;
|
method: string;
|
||||||
params: any
|
params: any
|
||||||
|
|
@ -300,8 +306,12 @@ export class TeleReporterReceiver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onEnd(result: FullResult): Promise<void> | void {
|
private _onEnd(result: JsonFullResult): Promise<void> | void {
|
||||||
return this._reporter.onEnd?.(result);
|
return this._reporter.onEnd?.({
|
||||||
|
status: result.status,
|
||||||
|
startTime: new Date(result.startTime),
|
||||||
|
duration: result.duration,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onExit(): Promise<void> | void {
|
private _onExit(): Promise<void> | void {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { colors, ms as milliseconds, parseStackTraceLine } from 'playwright-core
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter';
|
import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter';
|
||||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||||
import { getPackageManagerExecCommand, monotonicTime } from 'playwright-core/lib/utils';
|
import { getPackageManagerExecCommand } from 'playwright-core/lib/utils';
|
||||||
import type { ReporterV2 } from './reporterV2';
|
import type { ReporterV2 } from './reporterV2';
|
||||||
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
||||||
export const kOutputSymbol = Symbol('output');
|
export const kOutputSymbol = Symbol('output');
|
||||||
|
|
@ -45,13 +45,11 @@ type TestSummary = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class BaseReporter implements ReporterV2 {
|
export class BaseReporter implements ReporterV2 {
|
||||||
duration = 0;
|
|
||||||
config!: FullConfig;
|
config!: FullConfig;
|
||||||
suite!: Suite;
|
suite!: Suite;
|
||||||
totalTestCount = 0;
|
totalTestCount = 0;
|
||||||
result!: FullResult;
|
result!: FullResult;
|
||||||
private fileDurations = new Map<string, number>();
|
private fileDurations = new Map<string, number>();
|
||||||
private monotonicStartTime: number = 0;
|
|
||||||
private _omitFailures: boolean;
|
private _omitFailures: boolean;
|
||||||
private readonly _ttyWidthForTest: number;
|
private readonly _ttyWidthForTest: number;
|
||||||
private _fatalErrors: TestError[] = [];
|
private _fatalErrors: TestError[] = [];
|
||||||
|
|
@ -71,7 +69,6 @@ export class BaseReporter implements ReporterV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
onBegin(suite: Suite) {
|
onBegin(suite: Suite) {
|
||||||
this.monotonicStartTime = monotonicTime();
|
|
||||||
this.suite = suite;
|
this.suite = suite;
|
||||||
this.totalTestCount = suite.allTests().length;
|
this.totalTestCount = suite.allTests().length;
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +111,6 @@ export class BaseReporter implements ReporterV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnd(result: FullResult) {
|
async onEnd(result: FullResult) {
|
||||||
this.duration = monotonicTime() - this.monotonicStartTime;
|
|
||||||
this.result = result;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,7 +178,7 @@ export class BaseReporter implements ReporterV2 {
|
||||||
if (skipped)
|
if (skipped)
|
||||||
tokens.push(colors.yellow(` ${skipped} skipped`));
|
tokens.push(colors.yellow(` ${skipped} skipped`));
|
||||||
if (expected)
|
if (expected)
|
||||||
tokens.push(colors.green(` ${expected} passed`) + colors.dim(` (${milliseconds(this.duration)})`));
|
tokens.push(colors.green(` ${expected} passed`) + colors.dim(` (${milliseconds(this.result.duration)})`));
|
||||||
if (this.result.status === 'timedout')
|
if (this.result.status === 'timedout')
|
||||||
tokens.push(colors.red(` Timed out waiting ${this.config.globalTimeout / 1000}s for the entire test run`));
|
tokens.push(colors.red(` Timed out waiting ${this.config.globalTimeout / 1000}s for the entire test run`));
|
||||||
if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0)
|
if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import type { TransformCallback } from 'stream';
|
||||||
import { Transform } from 'stream';
|
import { Transform } from 'stream';
|
||||||
import { toPosixPath } from './json';
|
import { toPosixPath } from './json';
|
||||||
import { codeFrameColumns } from '../transform/babelBundle';
|
import { codeFrameColumns } from '../transform/babelBundle';
|
||||||
import type { FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic } from '../../types/testReporter';
|
import type { FullResult, FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic } from '../../types/testReporter';
|
||||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||||
import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils';
|
import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils';
|
||||||
import { formatResultFailure, stripAnsiEscapes } from './base';
|
import { formatResultFailure, stripAnsiEscapes } from './base';
|
||||||
|
|
@ -40,7 +40,6 @@ type TestEntry = {
|
||||||
testCaseSummary: TestCaseSummary
|
testCaseSummary: TestCaseSummary
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const htmlReportOptions = ['always', 'never', 'on-failure'];
|
const htmlReportOptions = ['always', 'never', 'on-failure'];
|
||||||
type HtmlReportOpenOption = (typeof htmlReportOptions)[number];
|
type HtmlReportOpenOption = (typeof htmlReportOptions)[number];
|
||||||
|
|
||||||
|
|
@ -112,11 +111,11 @@ class HtmlReporter extends EmptyReporter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
override async onEnd() {
|
override async onEnd(result: FullResult) {
|
||||||
const projectSuites = this.suite.suites;
|
const projectSuites = this.suite.suites;
|
||||||
await removeFolders([this._outputFolder]);
|
await removeFolders([this._outputFolder]);
|
||||||
const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL);
|
const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL);
|
||||||
this._buildResult = await builder.build(this.config.metadata, projectSuites);
|
this._buildResult = await builder.build(this.config.metadata, projectSuites, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async onExit() {
|
override async onExit() {
|
||||||
|
|
@ -218,7 +217,7 @@ class HtmlBuilder {
|
||||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async build(metadata: Metadata, projectSuites: Suite[]): Promise<{ ok: boolean, singleTestId: string | undefined }> {
|
async build(metadata: Metadata, projectSuites: Suite[], result: FullResult): Promise<{ ok: boolean, singleTestId: string | undefined }> {
|
||||||
|
|
||||||
const data = new Map<string, { testFile: TestFile, testFileSummary: TestFileSummary }>();
|
const data = new Map<string, { testFile: TestFile, testFileSummary: TestFileSummary }>();
|
||||||
for (const projectSuite of projectSuites) {
|
for (const projectSuite of projectSuites) {
|
||||||
|
|
@ -257,7 +256,6 @@ class HtmlBuilder {
|
||||||
if (test.outcome === 'flaky')
|
if (test.outcome === 'flaky')
|
||||||
++stats.flaky;
|
++stats.flaky;
|
||||||
++stats.total;
|
++stats.total;
|
||||||
stats.duration += test.duration;
|
|
||||||
}
|
}
|
||||||
stats.ok = stats.unexpected + stats.flaky === 0;
|
stats.ok = stats.unexpected + stats.flaky === 0;
|
||||||
if (!stats.ok)
|
if (!stats.ok)
|
||||||
|
|
@ -274,9 +272,11 @@ class HtmlBuilder {
|
||||||
}
|
}
|
||||||
const htmlReport: HTMLReport = {
|
const htmlReport: HTMLReport = {
|
||||||
metadata,
|
metadata,
|
||||||
|
startTime: result.startTime.getTime(),
|
||||||
|
duration: result.duration,
|
||||||
files: [...data.values()].map(e => e.testFileSummary),
|
files: [...data.values()].map(e => e.testFileSummary),
|
||||||
projectNames: projectSuites.map(r => r.project()!.name),
|
projectNames: projectSuites.map(r => r.project()!.name),
|
||||||
stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()), duration: metadata.totalTime }
|
stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) }
|
||||||
};
|
};
|
||||||
htmlReport.files.sort((f1, f2) => {
|
htmlReport.files.sort((f1, f2) => {
|
||||||
const w1 = f1.stats.unexpected * 1000 + f1.stats.flaky;
|
const w1 = f1.stats.unexpected * 1000 + f1.stats.flaky;
|
||||||
|
|
@ -501,7 +501,6 @@ const emptyStats = (): Stats => {
|
||||||
flaky: 0,
|
flaky: 0,
|
||||||
skipped: 0,
|
skipped: 0,
|
||||||
ok: true,
|
ok: true,
|
||||||
duration: 0,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -512,7 +511,6 @@ const addStats = (stats: Stats, delta: Stats): Stats => {
|
||||||
stats.unexpected += delta.unexpected;
|
stats.unexpected += delta.unexpected;
|
||||||
stats.flaky += delta.flaky;
|
stats.flaky += delta.flaky;
|
||||||
stats.ok = stats.ok && delta.ok;
|
stats.ok = stats.ok && delta.ok;
|
||||||
stats.duration += delta.duration;
|
|
||||||
return stats;
|
return stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@ import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep
|
||||||
import { Suite } from '../common/test';
|
import { Suite } from '../common/test';
|
||||||
import { prepareErrorStack, relativeFilePath } from './base';
|
import { prepareErrorStack, relativeFilePath } from './base';
|
||||||
import type { ReporterV2 } from './reporterV2';
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
import { monotonicTime } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
export class InternalReporter implements ReporterV2 {
|
export class InternalReporter {
|
||||||
private _reporter: ReporterV2;
|
private _reporter: ReporterV2;
|
||||||
private _didBegin = false;
|
private _didBegin = false;
|
||||||
private _config!: FullConfig;
|
private _config!: FullConfig;
|
||||||
|
private _startTime: Date | undefined;
|
||||||
|
private _monotonicStartTime: number | undefined;
|
||||||
|
|
||||||
constructor(reporter: ReporterV2) {
|
constructor(reporter: ReporterV2) {
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
|
|
@ -37,6 +40,8 @@ export class InternalReporter implements ReporterV2 {
|
||||||
|
|
||||||
onConfigure(config: FullConfig) {
|
onConfigure(config: FullConfig) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
|
this._startTime = new Date();
|
||||||
|
this._monotonicStartTime = monotonicTime();
|
||||||
this._reporter.onConfigure(config);
|
this._reporter.onConfigure(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,12 +67,16 @@ export class InternalReporter implements ReporterV2 {
|
||||||
this._reporter.onTestEnd(test, result);
|
this._reporter.onTestEnd(test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnd(result: FullResult) {
|
async onEnd(result: { status: FullResult['status'] }) {
|
||||||
if (!this._didBegin) {
|
if (!this._didBegin) {
|
||||||
// onBegin was not reported, emit it.
|
// onBegin was not reported, emit it.
|
||||||
this.onBegin(new Suite('', 'root'));
|
this.onBegin(new Suite('', 'root'));
|
||||||
}
|
}
|
||||||
await this._reporter.onEnd(result);
|
await this._reporter.onEnd({
|
||||||
|
...result,
|
||||||
|
startTime: this._startTime!,
|
||||||
|
duration: monotonicTime() - this._monotonicStartTime!,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onExit() {
|
async onExit() {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,8 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { ReporterDescription } from '../../types/test';
|
import type { ReporterDescription } from '../../types/test';
|
||||||
import type { FullResult } from '../../types/testReporter';
|
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver';
|
import type { JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver';
|
||||||
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
||||||
import { JsonStringInternalizer, StringInternPool } from '../isomorphic/stringInternPool';
|
import { JsonStringInternalizer, StringInternPool } from '../isomorphic/stringInternPool';
|
||||||
import { createReporters } from '../runner/reporters';
|
import { createReporters } from '../runner/reporters';
|
||||||
|
|
@ -228,7 +227,6 @@ function mergeConfigureEvents(configureEvents: JsonEvent[]): JsonEvent {
|
||||||
globalTimeout: 0,
|
globalTimeout: 0,
|
||||||
maxFailures: 0,
|
maxFailures: 0,
|
||||||
metadata: {
|
metadata: {
|
||||||
totalTime: 0,
|
|
||||||
},
|
},
|
||||||
rootDir: '',
|
rootDir: '',
|
||||||
version: '',
|
version: '',
|
||||||
|
|
@ -252,7 +250,6 @@ function mergeConfigs(to: JsonConfig, from: JsonConfig): JsonConfig {
|
||||||
metadata: {
|
metadata: {
|
||||||
...to.metadata,
|
...to.metadata,
|
||||||
...from.metadata,
|
...from.metadata,
|
||||||
totalTime: to.metadata.totalTime + from.metadata.totalTime,
|
|
||||||
actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0),
|
actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0),
|
||||||
},
|
},
|
||||||
workers: to.workers + from.workers,
|
workers: to.workers + from.workers,
|
||||||
|
|
@ -260,16 +257,26 @@ function mergeConfigs(to: JsonConfig, from: JsonConfig): JsonConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeEndEvents(endEvents: JsonEvent[]): JsonEvent {
|
function mergeEndEvents(endEvents: JsonEvent[]): JsonEvent {
|
||||||
const result: FullResult = { status: 'passed' };
|
let startTime = endEvents.length ? 10000000000000 : Date.now();
|
||||||
|
let status: JsonFullResult['status'] = 'passed';
|
||||||
|
let duration: number = 0;
|
||||||
|
|
||||||
for (const event of endEvents) {
|
for (const event of endEvents) {
|
||||||
const shardResult: FullResult = event.params.result;
|
const shardResult: JsonFullResult = event.params.result;
|
||||||
if (shardResult.status === 'failed')
|
if (shardResult.status === 'failed')
|
||||||
result.status = 'failed';
|
status = 'failed';
|
||||||
else if (shardResult.status === 'timedout' && result.status !== 'failed')
|
else if (shardResult.status === 'timedout' && status !== 'failed')
|
||||||
result.status = 'timedout';
|
status = 'timedout';
|
||||||
else if (shardResult.status === 'interrupted' && result.status !== 'failed' && result.status !== 'timedout')
|
else if (shardResult.status === 'interrupted' && status !== 'failed' && status !== 'timedout')
|
||||||
result.status = 'interrupted';
|
status = 'interrupted';
|
||||||
|
startTime = Math.min(startTime, shardResult.startTime);
|
||||||
|
duration = Math.max(duration, shardResult.duration);
|
||||||
}
|
}
|
||||||
|
const result: JsonFullResult = {
|
||||||
|
status,
|
||||||
|
startTime,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
method: 'onEnd',
|
method: 'onEnd',
|
||||||
params: {
|
params: {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||||
import type { FullConfig, FullResult, Location, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
import type { FullConfig, FullResult, Location, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||||
import { FullConfigInternal, getProjectId } from '../common/config';
|
import { FullConfigInternal, getProjectId } from '../common/config';
|
||||||
import type { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
import type { JsonAttachment, JsonConfig, JsonEvent, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
import type { JsonAttachment, JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||||
import type { ReporterV2 } from './reporterV2';
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
|
||||||
|
|
@ -125,7 +125,17 @@ export class TeleReporterEmitter implements ReporterV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnd(result: FullResult) {
|
async onEnd(result: FullResult) {
|
||||||
this._messageSink({ method: 'onEnd', params: { result } });
|
const resultPayload: JsonFullResult = {
|
||||||
|
status: result.status,
|
||||||
|
startTime: result.startTime.getTime(),
|
||||||
|
duration: result.duration,
|
||||||
|
};
|
||||||
|
this._messageSink({
|
||||||
|
method: 'onEnd',
|
||||||
|
params: {
|
||||||
|
result: resultPayload
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async onExit() {
|
async onExit() {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGloba
|
||||||
import type { Matcher } from '../util';
|
import type { Matcher } from '../util';
|
||||||
import type { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
import { buildDependentProjects, buildTeardownToSetupsMap } from './projectUtils';
|
import { buildDependentProjects, buildTeardownToSetupsMap } from './projectUtils';
|
||||||
import { monotonicTime } from 'playwright-core/lib/utils';
|
|
||||||
|
|
||||||
const readDirAsync = promisify(fs.readdir);
|
const readDirAsync = promisify(fs.readdir);
|
||||||
|
|
||||||
|
|
@ -105,15 +104,11 @@ export function createTaskRunnerForList(config: FullConfigInternal, reporter: Re
|
||||||
}
|
}
|
||||||
|
|
||||||
function createReportBeginTask(): Task<TestRun> {
|
function createReportBeginTask(): Task<TestRun> {
|
||||||
let montonicStartTime = 0;
|
|
||||||
return {
|
return {
|
||||||
setup: async ({ config, reporter, rootSuite }) => {
|
setup: async ({ reporter, rootSuite }) => {
|
||||||
montonicStartTime = monotonicTime();
|
|
||||||
reporter.onBegin(rootSuite!);
|
reporter.onBegin(rootSuite!);
|
||||||
},
|
},
|
||||||
teardown: async ({ config }) => {
|
teardown: async ({}) => {},
|
||||||
config.config.metadata.totalTime = monotonicTime() - montonicStartTime;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,16 @@ export interface FullResult {
|
||||||
* - 'interrupted' - interrupted by the user.
|
* - 'interrupted' - interrupted by the user.
|
||||||
*/
|
*/
|
||||||
status: 'passed' | 'failed' | 'timedout' | 'interrupted';
|
status: 'passed' | 'failed' | 'timedout' | 'interrupted';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test start wall time.
|
||||||
|
*/
|
||||||
|
startTime: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test duration in milliseconds.
|
||||||
|
*/
|
||||||
|
duration: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -996,7 +996,6 @@ test('preserve config fields', async ({ runInlineTest, mergeReports }) => {
|
||||||
expect(json.globalTimeout).toBe(config.globalTimeout);
|
expect(json.globalTimeout).toBe(config.globalTimeout);
|
||||||
expect(json.maxFailures).toBe(config.maxFailures);
|
expect(json.maxFailures).toBe(config.maxFailures);
|
||||||
expect(json.metadata).toEqual(expect.objectContaining(config.metadata));
|
expect(json.metadata).toEqual(expect.objectContaining(config.metadata));
|
||||||
expect(json.metadata.totalTime).toBeTruthy();
|
|
||||||
expect(json.workers).toBe(2);
|
expect(json.workers).toBe(2);
|
||||||
expect(json.version).toBeTruthy();
|
expect(json.version).toBeTruthy();
|
||||||
expect(json.version).not.toEqual(test.info().config.version);
|
expect(json.version).not.toEqual(test.info().config.version);
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,16 @@ export interface FullResult {
|
||||||
* - 'interrupted' - interrupted by the user.
|
* - 'interrupted' - interrupted by the user.
|
||||||
*/
|
*/
|
||||||
status: 'passed' | 'failed' | 'timedout' | 'interrupted';
|
status: 'passed' | 'failed' | 'timedout' | 'interrupted';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test start wall time.
|
||||||
|
*/
|
||||||
|
startTime: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test duration in milliseconds.
|
||||||
|
*/
|
||||||
|
duration: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Reporter {
|
export interface Reporter {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue