Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/src/test-reporters-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,23 @@ export default defineConfig({
});
```

You can omit test tags that are automatically appended to test titles:

```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';

export default defineConfig({
reporter: [['list', { omitTags: true }]],
});
```

List report supports the following configuration options and environment variables:

| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_LIST_PRINT_STEPS` | `printSteps` | Whether to print each step on its own line. | `false`
| `PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE` | `printFailuresInline` | Whether to print failure details immediately after a failed test instead of at the end. | `false`
| `PLAYWRIGHT_LIST_OMIT_TAGS` | `omitTags` | Whether to omit test tags that are automatically appended to test titles. | `false`
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
| `NO_COLOR` | | Whether to disable colored output ([no-color.org](https://no-color.org/)). Any non-empty value disables colors. | unset
Expand Down Expand Up @@ -152,6 +163,7 @@ Line report supports the following configuration options and environment variabl

| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_LINE_OMIT_TAGS` | `omitTags` | Whether to omit test tags that are automatically appended to test titles. | `false`
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
| `NO_COLOR` | | Whether to disable colored output ([no-color.org](https://no-color.org/)). Any non-empty value disables colors. | unset
Expand Down Expand Up @@ -195,6 +207,7 @@ Dot report supports the following configuration options and environment variable

| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_DOT_OMIT_TAGS` | `omitTags` | Whether to omit test tags that are automatically appended to test titles. | `false`
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
| `NO_COLOR` | | Whether to disable colored output ([no-color.org](https://no-color.org/)). Any non-empty value disables colors. | unset
Expand Down Expand Up @@ -414,6 +427,7 @@ JUnit report supports following configuration options and environment variables:
| `PLAYWRIGHT_JUNIT_OUTPUT_FILE` | `outputFile` | Full path to the output file. If defined, `PLAYWRIGHT_JUNIT_OUTPUT_DIR` and `PLAYWRIGHT_JUNIT_OUTPUT_NAME` will be ignored. | JUnit report is printed to the stdout.
| `PLAYWRIGHT_JUNIT_STRIP_ANSI` | `stripANSIControlSequences` | Whether to remove ANSI control sequences from the text before writing it in the report. | By default output text is added as is.
| `PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME` | `includeProjectInTestName` | Whether to include Playwright project name in every test case as a name prefix. | By default not included.
| `PLAYWRIGHT_JUNIT_OMIT_TAGS` | `omitTags` | Whether to omit test tags that are automatically appended to failure details. | `false`
| `PLAYWRIGHT_JUNIT_SUITE_ID` | | Value of the `id` attribute on the root `<testsuites/>` report entry. | Empty string.
| `PLAYWRIGHT_JUNIT_SUITE_NAME` | | Value of the `name` attribute on the root `<testsuites/>` report entry. | Empty string.

Expand All @@ -435,6 +449,8 @@ export default defineConfig({
});
```

The `github` reporter accepts `omitTags` (or the `PLAYWRIGHT_GITHUB_OMIT_TAGS` environment variable) to suppress test tags in its annotations, for example `reporter: [['github', { omitTags: true }]]`.

## Custom reporters

You can create a custom reporter by implementing a class with some of the reporter methods. Learn more about the [Reporter] API.
Expand Down
11 changes: 6 additions & 5 deletions packages/playwright/src/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export type TerminalReporterOptions = {
screen?: TerminalScreen;
omitFailures?: boolean;
includeTestId?: boolean;
omitTags?: boolean;
};

export class TerminalReporter implements ReporterV2 {
Expand Down Expand Up @@ -366,7 +367,7 @@ export class TerminalReporter implements ReporterV2 {
}

formatTestHeader(test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error' } = {}): string {
return formatTestHeader(this.screen, this.config, test, { ...options, includeTestId: this._options.includeTestId });
return formatTestHeader(this.screen, this.config, test, { ...options, includeTestId: this._options.includeTestId, omitTags: this._options.omitTags });
}

formatFailure(test: TestCase, index?: number): string {
Expand Down Expand Up @@ -407,7 +408,7 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
if (!errors.length)
continue;
if (!printedHeader) {
const header = formatTestHeader(screen, config, test, { indent: ' ', index, mode: 'error', includeTestId: options?.includeTestId });
const header = formatTestHeader(screen, config, test, { indent: ' ', index, mode: 'error', includeTestId: options?.includeTestId, omitTags: options?.omitTags });
lines.push(screen.colors.red(header));
printedHeader = true;
}
Expand Down Expand Up @@ -539,19 +540,19 @@ export function stepSuffix(step: TestStep | undefined) {
return stepTitles.map(t => t.split('\n')[0]).map(t => ' › ' + t).join('');
}

function formatTestTitle(screen: Screen, config: FullConfig, test: TestCase, step?: TestStep, options: { includeTestId?: boolean } = {}): string {
function formatTestTitle(screen: Screen, config: FullConfig, test: TestCase, step?: TestStep, options: { includeTestId?: boolean, omitTags?: boolean } = {}): string {
// root, project, file, ...describes, test
const [, projectName, , ...titles] = test.titlePath();
const location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`;
const testId = options.includeTestId ? `[id=${test.id}] ` : '';
const projectLabel = options.includeTestId ? `project=` : '';
const projectTitle = projectName ? `[${projectLabel}${projectName}] › ` : '';
const testTitle = `${testId}${projectTitle}${location} › ${titles.join(' › ')}`;
const extraTags = test.tags.filter(t => !testTitle.includes(t) && !config.tags.includes(t));
const extraTags = options.omitTags ? [] : test.tags.filter(t => !testTitle.includes(t) && !config.tags.includes(t));
return `${testTitle}${stepSuffix(step)}${extraTags.length ? ' ' + extraTags.join(' ') : ''}`;
}

function formatTestHeader(screen: Screen, config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error', includeTestId?: boolean } = {}): string {
function formatTestHeader(screen: Screen, config: FullConfig, test: TestCase, options: { indent?: string, index?: number, mode?: 'default' | 'error', includeTestId?: boolean, omitTags?: boolean } = {}): string {
const title = formatTestTitle(screen, config, test, undefined, options);
const header = `${options.indent || ''}${options.index ? options.index + ') ' : ''}${title}`;
let fullHeader = header;
Expand Down
8 changes: 8 additions & 0 deletions packages/playwright/src/reporters/dot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@
* limitations under the License.
*/

import { getAsBooleanFromENV } from '@utils/env';

import { markErrorsAsReported, TerminalReporter } from './base';

import type { DotReporterOptions } from '../../types/test';
import type { FullResult, Suite, TestCase, TestError, TestResult } from '../../types/testReporter';
import type { CommonReporterOptions, TerminalReporterOptions } from './base';

class DotReporter extends TerminalReporter {
private _counter = 0;

constructor(options?: DotReporterOptions & CommonReporterOptions & TerminalReporterOptions) {
super({ ...options, omitTags: getAsBooleanFromENV('PLAYWRIGHT_DOT_OMIT_TAGS', options?.omitTags) });
}

override onBegin(suite: Suite) {
super.onBegin(suite);
this.writeLine(this.generateStartingMessage());
Expand Down
6 changes: 4 additions & 2 deletions packages/playwright/src/reporters/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import path from 'path';

import { noColors } from '@isomorphic/colors';
import { msToString } from '@isomorphic/formatUtils';
import { getAsBooleanFromENV } from '@utils/env';

import { TerminalReporter, formatResultFailure, formatRetry } from './base';
import { stripAnsiEscapes } from '../util';

import type { TerminalReporterOptions } from './base';
import type { FullResult, TestCase, TestError, TestResult } from '../../types/testReporter';

type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error';
Expand Down Expand Up @@ -71,8 +73,8 @@ export class GitHubReporter extends TerminalReporter {
githubLogger = new GitHubLogger();
private _failedTestCount = 0;

constructor(options: { omitFailures?: boolean } = {}) {
super(options);
constructor(options: TerminalReporterOptions = {}) {
super({ ...options, omitTags: getAsBooleanFromENV('PLAYWRIGHT_GITHUB_OMIT_TAGS', options.omitTags) });
this.screen = { ...this.screen, colors: noColors };
}

Expand Down
6 changes: 4 additions & 2 deletions packages/playwright/src/reporters/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ class JUnitReporter implements ReporterV2 {
private stripANSIControlSequences = false;
private includeProjectInTestName = false;
private includeRetries = false;
private omitTags = false;

constructor(options: JUnitReporterOptions & CommonReporterOptions) {
this.stripANSIControlSequences = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_STRIP_ANSI', !!options.stripANSIControlSequences);
this.includeProjectInTestName = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME', !!options.includeProjectInTestName);
this.includeRetries = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_INCLUDE_RETRIES', !!options.includeRetries);
this.omitTags = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_OMIT_TAGS', !!options.omitTags);
this.configDir = options.configDir;
this.resolvedOutputFile = resolveOutputFile('JUNIT', options)?.outputFile;
}
Expand Down Expand Up @@ -222,7 +224,7 @@ class JUnitReporter implements ReporterV2 {
entry.children!.push({
name: errorInfo.elementName,
attributes: { message: errorInfo.message, type: errorInfo.type },
text: stripAnsiEscapes(formatFailure(nonTerminalScreen, this.config, test))
text: stripAnsiEscapes(formatFailure(nonTerminalScreen, this.config, test, undefined, { omitTags: this.omitTags }))
});
return errorInfo.elementName;
}
Expand All @@ -232,7 +234,7 @@ class JUnitReporter implements ReporterV2 {
message: `${path.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`,
type: 'FAILURE',
},
text: stripAnsiEscapes(formatFailure(nonTerminalScreen, this.config, test))
text: stripAnsiEscapes(formatFailure(nonTerminalScreen, this.config, test, undefined, { omitTags: this.omitTags }))
});
return 'failure';
}
Expand Down
8 changes: 8 additions & 0 deletions packages/playwright/src/reporters/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,24 @@
* limitations under the License.
*/

import { getAsBooleanFromENV } from '@utils/env';

import { markErrorsAsReported, TerminalReporter } from './base';

import type { LineReporterOptions } from '../../types/test';
import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
import type { CommonReporterOptions, TerminalReporterOptions } from './base';

class LineReporter extends TerminalReporter {
private _current = 0;
private _failures = 0;
private _lastTest: TestCase | undefined;
private _didBegin = false;

constructor(options?: LineReporterOptions & CommonReporterOptions & TerminalReporterOptions) {
super({ ...options, omitTags: getAsBooleanFromENV('PLAYWRIGHT_LINE_OMIT_TAGS', options?.omitTags) });
}

override onBegin(suite: Suite) {
super.onBegin(suite);
const startingMessage = this.generateStartingMessage();
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/reporters/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ListReporter extends TerminalReporter {
private _paused = new Set<TestResult>();

constructor(options?: ListReporterOptions & CommonReporterOptions & TerminalReporterOptions) {
super(options);
super({ ...options, omitTags: getAsBooleanFromENV('PLAYWRIGHT_LIST_OMIT_TAGS', options?.omitTags) });
this._printSteps = getAsBooleanFromENV('PLAYWRIGHT_LIST_PRINT_STEPS', options?.printSteps);
this._printFailuresInline = getAsBooleanFromENV('PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE', options?.printFailuresInline);
}
Expand Down
13 changes: 8 additions & 5 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions,
export * from 'playwright-core';

export type BlobReporterOptions = { outputDir?: string, fileName?: string };
export type ListReporterOptions = { printSteps?: boolean, printFailuresInline?: boolean };
export type JUnitReporterOptions = { outputFile?: string, stripANSIControlSequences?: boolean, includeProjectInTestName?: boolean, includeRetries?: boolean };
export type DotReporterOptions = { omitTags?: boolean };
export type LineReporterOptions = { omitTags?: boolean };
export type ListReporterOptions = { printSteps?: boolean, printFailuresInline?: boolean, omitTags?: boolean };
export type GitHubReporterOptions = { omitTags?: boolean };
export type JUnitReporterOptions = { outputFile?: string, stripANSIControlSequences?: boolean, includeProjectInTestName?: boolean, includeRetries?: boolean, omitTags?: boolean };
export type JsonReporterOptions = { outputFile?: string };
export type HtmlReporterOptions = {
outputFolder?: string;
Expand All @@ -37,10 +40,10 @@ export type HtmlReporterOptions = {

export type ReporterDescription = Readonly<
['blob'] | ['blob', BlobReporterOptions] |
['dot'] |
['line'] |
['dot'] | ['dot', DotReporterOptions] |
['line'] | ['line', LineReporterOptions] |
['list'] | ['list', ListReporterOptions] |
['github'] |
['github'] | ['github', GitHubReporterOptions] |
['junit'] | ['junit', JUnitReporterOptions] |
['json'] | ['json', JsonReporterOptions] |
['html'] | ['html', HtmlReporterOptions] |
Expand Down
101 changes: 101 additions & 0 deletions tests/playwright-test/reporter-base.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,5 +486,106 @@ for (const useIntermediateMergeReport of [false, true] as const) {
expect(text).toContain('› passes @bar1 @bar2 (');
expect(text).toContain('› passes @baz1 @baz2 (');
});

test('should omit tags when omitTags is set', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
reporter: [['list', { omitTags: true }]],
};
`,
'a.test.ts': `
const { test, expect } = require('@playwright/test');
test('passes', { tag: ['@foo1', '@foo2'] }, async ({}) => {
expect(0).toBe(0);
});
test('passes @bar1 @bar2', async ({}) => {
expect(0).toBe(0);
});
test('passes @baz1', { tag: ['@baz2'] }, async ({}) => {
expect(0).toBe(0);
});
`,
});
const text = stripAnsi(result.output);
// Tags appended from the `tag` annotation are omitted.
expect(text).toContain('› passes (');
expect(text).not.toContain('@foo1');
expect(text).not.toContain('@foo2');
expect(text).not.toContain('@baz2');
// Tags authored directly in the title are preserved.
expect(text).toContain('› passes @bar1 @bar2 (');
expect(text).toContain('› passes @baz1 (');
expect(result.exitCode).toBe(0);
});

test('should omit tags from failures when omitTags is set', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
reporter: [['list', { omitTags: true }]],
};
`,
'a.test.ts': `
const { test, expect } = require('@playwright/test');
test('fails', { tag: ['@qux1'] }, async ({}) => {
expect(1).toBe(2);
});
`,
});
const text = stripAnsi(result.output);
const titleLines = text.split('\n').filter(line => line.includes('a.test.ts') && line.includes('› fails'));
expect(titleLines.length).toBeGreaterThan(0);
for (const line of titleLines)
expect(line).not.toContain('@qux1');
expect(result.exitCode).toBe(1);
});

test('should omit tags via the PLAYWRIGHT_LIST_OMIT_TAGS environment variable', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
reporter: [['list']],
};
`,
'a.test.ts': `
const { test, expect } = require('@playwright/test');
test('passes', { tag: ['@foo1', '@foo2'] }, async ({}) => {
expect(0).toBe(0);
});
test('passes @bar1 @bar2', async ({}) => {
expect(0).toBe(0);
});
`,
}, undefined, { PLAYWRIGHT_LIST_OMIT_TAGS: '1' });
const text = stripAnsi(result.output);
// Tags appended from the `tag` annotation are omitted.
expect(text).toContain('› passes (');
expect(text).not.toContain('@foo1');
expect(text).not.toContain('@foo2');
// Tags authored directly in the title are preserved.
expect(text).toContain('› passes @bar1 @bar2 (');
expect(result.exitCode).toBe(0);
});

test('should let PLAYWRIGHT_LIST_OMIT_TAGS override the omitTags option', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
reporter: [['list', { omitTags: true }]],
};
`,
'a.test.ts': `
const { test, expect } = require('@playwright/test');
test('passes', { tag: ['@foo1', '@foo2'] }, async ({}) => {
expect(0).toBe(0);
});
`,
}, undefined, { PLAYWRIGHT_LIST_OMIT_TAGS: '0' });
const text = stripAnsi(result.output);
// The environment variable disables omitTags, so the appended tags are shown.
expect(text).toContain('› passes @foo1 @foo2 (');
expect(result.exitCode).toBe(0);
});
});
}
13 changes: 8 additions & 5 deletions utils/generate_types/overrides-test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import type { APIRequestContext, Browser, BrowserContext, BrowserContextOptions,
export * from 'playwright-core';

export type BlobReporterOptions = { outputDir?: string, fileName?: string };
export type ListReporterOptions = { printSteps?: boolean, printFailuresInline?: boolean };
export type JUnitReporterOptions = { outputFile?: string, stripANSIControlSequences?: boolean, includeProjectInTestName?: boolean, includeRetries?: boolean };
export type DotReporterOptions = { omitTags?: boolean };
export type LineReporterOptions = { omitTags?: boolean };
export type ListReporterOptions = { printSteps?: boolean, printFailuresInline?: boolean, omitTags?: boolean };
export type GitHubReporterOptions = { omitTags?: boolean };
export type JUnitReporterOptions = { outputFile?: string, stripANSIControlSequences?: boolean, includeProjectInTestName?: boolean, includeRetries?: boolean, omitTags?: boolean };
export type JsonReporterOptions = { outputFile?: string };
export type HtmlReporterOptions = {
outputFolder?: string;
Expand All @@ -36,10 +39,10 @@ export type HtmlReporterOptions = {

export type ReporterDescription = Readonly<
['blob'] | ['blob', BlobReporterOptions] |
['dot'] |
['line'] |
['dot'] | ['dot', DotReporterOptions] |
['line'] | ['line', LineReporterOptions] |
['list'] | ['list', ListReporterOptions] |
['github'] |
['github'] | ['github', GitHubReporterOptions] |
['junit'] | ['junit', JUnitReporterOptions] |
['json'] | ['json', JsonReporterOptions] |
['html'] | ['html', HtmlReporterOptions] |
Expand Down
Loading