feat(config): failOnFlakyTests option (#35109)

This commit is contained in:
Jean-François Greffier 2025-03-11 18:06:20 +01:00 committed by GitHub
parent 8f5b8c10c6
commit 85a66912c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 74 additions and 3 deletions

View File

@ -10,6 +10,12 @@ Resolved configuration which is accessible via [`property: TestInfo.config`] and
Path to the configuration file used to run the tests. The value is an empty string if no config file was used. Path to the configuration file used to run the tests. The value is an empty string if no config file was used.
## property: FullConfig.failOnFlakyTests
* since: v1.52
- type: <[boolean]>
See [`property: TestConfig.failOnFlakyTests`].
## property: FullConfig.forbidOnly ## property: FullConfig.forbidOnly
* since: v1.10 * since: v1.10
- type: <[boolean]> - type: <[boolean]>

View File

@ -108,6 +108,24 @@ export default defineConfig({
}); });
``` ```
## property: TestConfig.failOnFlakyTests
* since: v1.52
- type: ?<[boolean]>
Whether to exit with an error if any tests are marked as flaky. Useful on CI.
Also available in the [command line](../test-cli.md) with the `--fail-on-flaky-tests` option.
**Usage**
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
export default defineConfig({
failOnFlakyTests: !!process.env.CI,
});
```
## property: TestConfig.forbidOnly ## property: TestConfig.forbidOnly
* since: v1.10 * since: v1.10
- type: ?<[boolean]> - type: ?<[boolean]>

View File

@ -56,7 +56,6 @@ export class FullConfigInternal {
cliProjectFilter?: string[]; cliProjectFilter?: string[];
cliListOnly = false; cliListOnly = false;
cliPassWithNoTests?: boolean; cliPassWithNoTests?: boolean;
cliFailOnFlakyTests?: boolean;
cliLastFailed?: boolean; cliLastFailed?: boolean;
testIdMatcher?: Matcher; testIdMatcher?: Matcher;
lastFailedTestIdMatcher?: Matcher; lastFailedTestIdMatcher?: Matcher;
@ -90,6 +89,7 @@ export class FullConfigInternal {
this.config = { this.config = {
configFile: resolvedConfigFile, configFile: resolvedConfigFile,
rootDir: pathResolve(configDir, userConfig.testDir) || configDir, rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
failOnFlakyTests: takeFirst(configCLIOverrides.failOnFlakyTests, userConfig.failOnFlakyTests, false),
forbidOnly: takeFirst(configCLIOverrides.forbidOnly, userConfig.forbidOnly, false), forbidOnly: takeFirst(configCLIOverrides.forbidOnly, userConfig.forbidOnly, false),
fullyParallel: takeFirst(configCLIOverrides.fullyParallel, userConfig.fullyParallel, false), fullyParallel: takeFirst(configCLIOverrides.fullyParallel, userConfig.fullyParallel, false),
globalSetup: this.globalSetups[0] ?? null, globalSetup: this.globalSetups[0] ?? null,

View File

@ -25,6 +25,7 @@ import type { SerializedCompilationCache } from '../transform/compilationCache'
export type ConfigCLIOverrides = { export type ConfigCLIOverrides = {
debug?: boolean; debug?: boolean;
failOnFlakyTests?: boolean;
forbidOnly?: boolean; forbidOnly?: boolean;
fullyParallel?: boolean; fullyParallel?: boolean;
globalTimeout?: number; globalTimeout?: number;

View File

@ -593,6 +593,7 @@ export class TeleTestResult implements reporterTypes.TestResult {
export type TeleFullProject = reporterTypes.FullProject; export type TeleFullProject = reporterTypes.FullProject;
export const baseFullConfig: reporterTypes.FullConfig = { export const baseFullConfig: reporterTypes.FullConfig = {
failOnFlakyTests: false,
forbidOnly: false, forbidOnly: false,
fullyParallel: false, fullyParallel: false,
globalSetup: null, globalSetup: null,

View File

@ -172,7 +172,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
config.cliListOnly = !!opts.list; config.cliListOnly = !!opts.list;
config.cliProjectFilter = opts.project || undefined; config.cliProjectFilter = opts.project || undefined;
config.cliPassWithNoTests = !!opts.passWithNoTests; config.cliPassWithNoTests = !!opts.passWithNoTests;
config.cliFailOnFlakyTests = !!opts.failOnFlakyTests;
config.cliLastFailed = !!opts.lastFailed; config.cliLastFailed = !!opts.lastFailed;
// Evaluate project filters against config before starting execution. This enables a consistent error message across run modes // Evaluate project filters against config before starting execution. This enables a consistent error message across run modes
@ -294,6 +293,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid
updateSnapshots = 'updateSnapshots' in options ? 'changed' : undefined; updateSnapshots = 'updateSnapshots' in options ? 'changed' : undefined;
const overrides: ConfigCLIOverrides = { const overrides: ConfigCLIOverrides = {
failOnFlakyTests: options.failOnFlakyTests ? true : undefined,
forbidOnly: options.forbidOnly ? true : undefined, forbidOnly: options.forbidOnly ? true : undefined,
fullyParallel: options.fullyParallel ? true : undefined, fullyParallel: options.fullyParallel ? true : undefined,
globalTimeout: options.globalTimeout ? parseInt(options.globalTimeout, 10) : undefined, globalTimeout: options.globalTimeout ? parseInt(options.globalTimeout, 10) : undefined,

View File

@ -49,7 +49,7 @@ export class FailureTracker {
} }
result(): 'failed' | 'passed' { result(): 'failed' | 'passed' {
return this._hasWorkerErrors || this.hasReachedMaxFailures() || this.hasFailedTests() || (this._config.cliFailOnFlakyTests && this.hasFlakyTests()) ? 'failed' : 'passed'; return this._hasWorkerErrors || this.hasReachedMaxFailures() || this.hasFailedTests() || (this._config.config.failOnFlakyTests && this.hasFlakyTests()) ? 'failed' : 'passed';
} }
hasFailedTests() { hasFailedTests() {
@ -63,4 +63,5 @@ export class FailureTracker {
maxFailures() { maxFailures() {
return this._config.config.maxFailures; return this._config.config.maxFailures;
} }
} }

View File

@ -1177,6 +1177,25 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
}; };
}; };
/**
* Whether to exit with an error if any tests are marked as flaky. Useful on CI.
*
* Also available in the [command line](https://playwright.dev/docs/test-cli) with the `--fail-on-flaky-tests` option.
*
* **Usage**
*
* ```js
* // playwright.config.ts
* import { defineConfig } from '@playwright/test';
*
* export default defineConfig({
* failOnFlakyTests: !!process.env.CI,
* });
* ```
*
*/
failOnFlakyTests?: boolean;
/** /**
* Whether to exit with an error if any tests or groups are marked as * Whether to exit with an error if any tests or groups are marked as
* [test.only(title[, details, body])](https://playwright.dev/docs/api/class-test#test-only) or * [test.only(title[, details, body])](https://playwright.dev/docs/api/class-test#test-only) or
@ -1911,6 +1930,12 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
*/ */
configFile?: string; configFile?: string;
/**
* See
* [testConfig.failOnFlakyTests](https://playwright.dev/docs/api/class-testconfig#test-config-fail-on-flaky-tests).
*/
failOnFlakyTests: boolean;
/** /**
* See [testConfig.forbidOnly](https://playwright.dev/docs/api/class-testconfig#test-config-forbid-only). * See [testConfig.forbidOnly](https://playwright.dev/docs/api/class-testconfig#test-config-forbid-only).
*/ */

View File

@ -72,6 +72,25 @@ test('should prioritize command line timeout over project timeout', async ({ run
expect(result.output).toContain('Test timeout of 500ms exceeded.'); expect(result.output).toContain('Test timeout of 500ms exceeded.');
}); });
test('should support failOnFlakyTests config option', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
failOnFlakyTests: true,
retries: 1
};
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('flake', async ({}, testInfo) => {
expect(testInfo.retry).toBe(1);
});
`,
}, { 'retries': 1 });
expect(result.exitCode).not.toBe(0);
expect(result.flaky).toBe(1);
});
test('should read config from --config, resolve relative testDir', async ({ runInlineTest }) => { test('should read config from --config, resolve relative testDir', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'my.config.ts': ` 'my.config.ts': `