mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(reporter): expose more apis (#9603)
This commit is contained in:
parent
6d727401bf
commit
6d554a5e30
@ -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]>>
|
||||
|
||||
|
||||
@ -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]>
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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}`;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
1
packages/playwright-test/types/test.d.ts
vendored
1
packages/playwright-test/types/test.d.ts
vendored
@ -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'`.
|
||||
|
||||
11
packages/playwright-test/types/testReporter.d.ts
vendored
11
packages/playwright-test/types/testReporter.d.ts
vendored
@ -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.
|
||||
|
||||
@ -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%%',
|
||||
|
||||
1
utils/generate_types/overrides-test.d.ts
vendored
1
utils/generate_types/overrides-test.d.ts
vendored
@ -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[];
|
||||
|
||||
@ -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[];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user