feat(reporter): expose more apis (#9603)

This commit is contained in:
Dmitry Gozman 2021-10-19 08:38:04 -07:00 committed by GitHub
parent 6d727401bf
commit 6d554a5e30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 69 additions and 24 deletions

View File

@ -29,6 +29,16 @@ Returns the list of all test cases in this suite and its descendants, as opposit
Location in the source where the suite is defined. Missing for root and project suites.
## property: Suite.parent
- type: <[void]|[Suite]>
Parent suite or [void] for the root suite.
## property: Suite.project
- type: <[void]|[TestProject]>
Configuration of the project this suite belongs to, or [void] for the root suite.
## property: Suite.suites
- type: <[Array]<[Suite]>>

View File

@ -53,6 +53,11 @@ The maximum number of retries given to this test in the configuration.
Learn more about [test retries](./test-retries.md#retries).
## property: TestCase.suite
- type: <[Suite]>
Suite this test case belongs to.
## property: TestCase.timeout
- type: <[float]>

View File

@ -230,7 +230,7 @@ export class Dispatcher {
retryCandidates.add(failedTestId);
let outermostSerialSuite: Suite | undefined;
for (let parent = this._testById.get(failedTestId)!.test.parent; parent; parent = parent.parent) {
for (let parent: Suite | undefined = this._testById.get(failedTestId)!.test.parent; parent; parent = parent.parent) {
if (parent._parallelMode === 'serial')
outermostSerialSuite = parent;
}
@ -241,7 +241,7 @@ export class Dispatcher {
// We have failed tests that belong to a serial suite.
// We should skip all future tests from the same serial suite.
remaining = remaining.filter(test => {
let parent = test.parent;
let parent: Suite | undefined = test.parent;
while (parent && !serialSuitesWithFailures.has(parent))
parent = parent.parent;

View File

@ -40,7 +40,7 @@ export class Loader {
constructor(defaultConfig: Config, configOverrides: Config) {
this._defaultConfig = defaultConfig;
this._configOverrides = configOverrides;
this._fullConfig = baseFullConfig;
this._fullConfig = { ...baseFullConfig };
}
static async deserialize(data: SerializedLoaderData): Promise<Loader> {
@ -426,6 +426,7 @@ const baseFullConfig: FullConfig = {
quiet: false,
shard: null,
updateSnapshots: 'missing',
version: require('../package.json').version,
workers: 1,
webServer: null,
};

View File

@ -58,7 +58,7 @@ export class ProjectImpl {
let pool = this.buildTestTypePool(test._testType);
const parents: Suite[] = [];
for (let parent = test.parent; parent; parent = parent.parent)
for (let parent: Suite | undefined = test.parent; parent; parent = parent.parent)
parents.push(parent);
parents.reverse();
@ -82,7 +82,6 @@ export class ProjectImpl {
private _cloneEntries(from: Suite, to: Suite, repeatEachIndex: number, filter: (test: TestCase) => boolean): boolean {
for (const hook of from._allHooks) {
const clone = hook._clone();
clone.projectName = this.config.name;
clone._pool = this.buildPool(hook);
clone._projectIndex = this.index;
to._addAllHook(clone);
@ -98,7 +97,6 @@ export class ProjectImpl {
} else {
const pool = this.buildPool(entry);
const test = entry._clone();
test.projectName = this.config.name;
test.retries = this.config.retries;
test._workerHash = `run${this.index}-${pool.digest}-repeat${repeatEachIndex}`;
test._id = `${entry._ordinalInFile}@${entry._requireFile}#run${this.index}-repeat${repeatEachIndex}`;

View File

@ -16,7 +16,6 @@
import fs from 'fs';
import path from 'path';
import { FullProject } from '../types';
import { FullConfig, Location, Suite, TestCase, TestResult, TestStatus, TestStep } from '../../types/testReporter';
import { assert, calculateSha1 } from 'playwright-core/src/utils/utils';
import { sanitizeForFilePath } from '../util';
@ -109,7 +108,7 @@ class RawReporter {
async onEnd() {
const projectSuites = this.suite.suites;
for (const suite of projectSuites) {
const project = (suite as any)._projectConfig as FullProject;
const project = suite.project();
assert(project, 'Internal Error: Invalid project structure');
const reportFolder = path.join(project.outputDir, 'report');
fs.mkdirSync(reportFolder, { recursive: true });
@ -132,7 +131,7 @@ class RawReporter {
generateProjectReport(config: FullConfig, suite: Suite): JsonReport {
this.config = config;
const project = (suite as any)._projectConfig as FullProject;
const project = suite.project();
assert(project, 'Internal Error: Invalid project structure');
const report: JsonReport = {
config,

View File

@ -509,7 +509,7 @@ function createTestGroups(rootSuite: Suite): TestGroup[] {
}
let insideParallel = false;
for (let parent = test.parent; parent; parent = parent.parent)
for (let parent: Suite | undefined = test.parent; parent; parent = parent.parent)
insideParallel = insideParallel || parent._parallelMode === 'parallel';
if (insideParallel) {

View File

@ -22,20 +22,12 @@ import { FullProject } from './types';
class Base {
title: string;
parent?: Suite;
_only = false;
_requireFile: string = '';
constructor(title: string) {
this.title = title;
}
titlePath(): string[] {
const titlePath = this.parent ? this.parent.titlePath() : [];
titlePath.push(this.title);
return titlePath;
}
}
export type Modifier = {
@ -49,6 +41,7 @@ export class Suite extends Base implements reporterTypes.Suite {
suites: Suite[] = [];
tests: TestCase[] = [];
location?: Location;
parent?: Suite;
_use: FixturesWithLocation[] = [];
_isDescribe = false;
_entries: (Suite | TestCase)[] = [];
@ -91,6 +84,12 @@ export class Suite extends Base implements reporterTypes.Suite {
return result;
}
titlePath(): string[] {
const titlePath = this.parent ? this.parent.titlePath() : [];
titlePath.push(this.title);
return titlePath;
}
_getOnlyItems(): (TestCase | Suite)[] {
const items: (TestCase | Suite)[] = [];
if (this._only)
@ -113,19 +112,24 @@ export class Suite extends Base implements reporterTypes.Suite {
suite._modifiers = this._modifiers.slice();
suite._isDescribe = this._isDescribe;
suite._parallelMode = this._parallelMode;
suite._projectConfig = this._projectConfig;
return suite;
}
project(): FullProject | undefined {
return this._projectConfig || this.parent?.project();
}
}
export class TestCase extends Base implements reporterTypes.TestCase {
fn: Function;
results: reporterTypes.TestResult[] = [];
location: Location;
parent!: Suite;
expectedStatus: reporterTypes.TestStatus = 'passed';
timeout = 0;
annotations: Annotations = [];
projectName = '';
retries = 0;
_type: 'beforeAll' | 'afterAll' | 'test';
@ -146,6 +150,12 @@ export class TestCase extends Base implements reporterTypes.TestCase {
this.location = location;
}
titlePath(): string[] {
const titlePath = this.parent ? this.parent.titlePath() : [];
titlePath.push(this.title);
return titlePath;
}
outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' {
const nonSkipped = this.results.filter(result => result.status !== 'skipped');
if (!nonSkipped.length)

View File

@ -323,7 +323,7 @@ export class WorkerRunner extends EventEmitter {
};
// Inherit test.setTimeout() from parent suites.
for (let suite = test.parent; suite; suite = suite.parent) {
for (let suite: Suite | undefined = test.parent; suite; suite = suite.parent) {
if (suite._timeout !== undefined) {
testInfo.setTimeout(suite._timeout);
break;
@ -420,7 +420,7 @@ export class WorkerRunner extends EventEmitter {
private async _runBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
try {
const beforeEachModifiers: Modifier[] = [];
for (let s = test.parent; s; s = s.parent) {
for (let s: Suite | undefined = test.parent; s; s = s.parent) {
const modifiers = s._modifiers.filter(modifier => !this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location));
beforeEachModifiers.push(...modifiers.reverse());
}

View File

@ -698,6 +698,7 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
* Also available in the [command line](https://playwright.dev/docs/test-cli) with the `--max-failures` and `-x` options.
*/
maxFailures: number;
version: string;
/**
* Whether to preserve test output in the
* [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Defaults to `'always'`.

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import type { FullConfig, TestStatus, TestError } from './test';
import type { FullConfig, FullProject, TestStatus, TestError } from './test';
export type { FullConfig, TestStatus, TestError } from './test';
/**
@ -57,6 +57,10 @@ export interface Location {
* [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin) method.
*/
export interface Suite {
/**
* Parent suite or [void] for the root suite.
*/
parent?: Suite;
/**
* Suite title.
* - Empty for root suite.
@ -89,6 +93,10 @@ export interface Suite {
* [suite.tests](https://playwright.dev/docs/api/class-suite#suite-tests).
*/
allTests(): TestCase[];
/**
* Configuration of the project this suite belongs to, or [void] for the root suite.
*/
project(): FullProject | undefined;
}
/**
@ -98,6 +106,7 @@ export interface Suite {
* or repeated multiple times, it will have multiple `TestCase` objects in corresponding projects' suites.
*/
export interface TestCase {
parent: Suite;
/**
* Test title as passed to the [test.(call)(title, testFunction)](https://playwright.dev/docs/api/class-test#test-call)
* call.

View File

@ -71,9 +71,16 @@ test('should work with custom reporter', async ({ runInlineTest }) => {
}
onBegin(config, suite) {
console.log('\\n%%reporter-begin-' + this.options.begin + '%%');
console.log('\\n%%version-' + config.version);
}
onTestBegin(test) {
console.log('\\n%%reporter-testbegin-' + test.title + '-' + test.titlePath()[1] + '%%');
const projectName = test.titlePath()[1];
console.log('\\n%%reporter-testbegin-' + test.title + '-' + projectName + '%%');
const suite = test.parent;
if (!suite.tests.includes(test))
console.log('\\n%%error-inconsistent-parent');
if (test.parent.project().name !== projectName)
console.log('\\n%%error-inconsistent-project-name');
}
onStdOut() {
console.log('\\n%%reporter-stdout%%');
@ -126,6 +133,7 @@ test('should work with custom reporter', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0);
expect(result.output.split('\n').filter(line => line.startsWith('%%'))).toEqual([
'%%reporter-begin-begin%%',
'%%version-' + require('../../packages/playwright-test/package.json').version,
'%%reporter-testbegin-is run-foo%%',
'%%reporter-stdout%%',
'%%reporter-stderr%%',

View File

@ -142,6 +142,7 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
grep: RegExp | RegExp[];
grepInvert: RegExp | RegExp[] | null;
maxFailures: number;
version: string;
preserveOutput: PreserveOutput;
projects: FullProject<TestArgs, WorkerArgs>[];
reporter: ReporterDescription[];

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { FullConfig, TestStatus, TestError } from './test';
import type { FullConfig, FullProject, TestStatus, TestError } from './test';
export type { FullConfig, TestStatus, TestError } from './test';
export interface Location {
@ -24,15 +24,18 @@ export interface Location {
}
export interface Suite {
parent?: Suite;
title: string;
location?: Location;
suites: Suite[];
tests: TestCase[];
titlePath(): string[];
allTests(): TestCase[];
project(): FullProject | undefined;
}
export interface TestCase {
parent: Suite;
title: string;
location: Location;
titlePath(): string[];