feat(config): failOnFlakyTests option
This patch adds a configuration option to make the test runner exit with a non-zero status when any test is flaky. The config option does not take precedence over the CLI flag. Fixes #34397
This commit is contained in:
parent
bd74fc4964
commit
39a2e9cf3e
|
|
@ -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.51
|
||||||
|
- type: <[boolean]>
|
||||||
|
|
||||||
|
See [`property: TestConfig.failOnFlakyTests`].
|
||||||
|
|
||||||
## property: FullConfig.forbidOnly
|
## property: FullConfig.forbidOnly
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: <[boolean]>
|
- type: <[boolean]>
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,24 @@ export default defineConfig({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## property: TestConfig.failOnFlakyTests
|
||||||
|
* since: v1.51
|
||||||
|
- 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 ? true : false,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## property: TestConfig.forbidOnly
|
## property: TestConfig.forbidOnly
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[boolean]>
|
- type: ?<[boolean]>
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -592,6 +592,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,
|
||||||
|
|
|
||||||
|
|
@ -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.failOnFlakyTests() && this.hasFlakyTests()) ? 'failed' : 'passed';
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFailedTests() {
|
hasFailedTests() {
|
||||||
|
|
@ -63,4 +63,8 @@ export class FailureTracker {
|
||||||
maxFailures() {
|
maxFailures() {
|
||||||
return this._config.config.maxFailures;
|
return this._config.config.maxFailures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
failOnFlakyTests() {
|
||||||
|
return this._config.config.failOnFlakyTests || this._config.cliFailOnFlakyTests;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
packages/playwright/types/test.d.ts
vendored
25
packages/playwright/types/test.d.ts
vendored
|
|
@ -1096,6 +1096,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 ? true : false,
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
|
@ -1855,6 +1874,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).
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,92 @@ 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 default to failOnFlakyTests false', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('flake', async ({}, testInfo) => {
|
||||||
|
expect(testInfo.retry).toBe(1);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).not.toBe(0);
|
||||||
|
expect(result.flaky).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should prioritize command line --fail-on-flaky-tests flag over config failOnFlakyTests', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
failOnFlakyTests: false
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.js': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('flake', async ({}, testInfo) => {
|
||||||
|
expect(testInfo.retry).toBe(1);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { 'retries': 1, 'fail-on-flaky-tests': true });
|
||||||
|
expect(result.exitCode).not.toBe(0);
|
||||||
|
expect(result.flaky).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 support failOnFlakyTests config option + retries CLI flag', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
failOnFlakyTests: true,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'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 support fail-on-flaky-tests CLI flag + retries config option', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
retries: 1,
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.js': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('flake', async ({}, testInfo) => {
|
||||||
|
expect(testInfo.retry).toBe(1);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { 'fail-on-flaky-tests': true });
|
||||||
|
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': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue