feat: add option to fail dependent tests

This commit is contained in:
JacksonLei123 2024-12-18 18:15:21 -05:00
parent 92e2d01329
commit 7004464433
8 changed files with 72 additions and 2 deletions

View file

@ -94,6 +94,7 @@ export class FullConfigInternal {
reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(userConfig.reporter, configDir), [[defaultReporter]]),
reportSlowTests: takeFirst(userConfig.reportSlowTests, { max: 5, threshold: 15000 }),
quiet: takeFirst(configCLIOverrides.quiet, userConfig.quiet, false),
failDependentTests: takeFirst(configCLIOverrides.failDependentTests, userConfig.failDependentTests, false),
projects: [],
shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, 'missing'),

View file

@ -30,6 +30,7 @@ export type ConfigCLIOverrides = {
outputDir?: string;
preserveOutputDir?: boolean;
quiet?: boolean;
failDependentTests?: boolean;
repeatEach?: number;
retries?: number;
reporter?: ReporterDescription[];

View file

@ -354,6 +354,24 @@ export class TestCase extends Base implements reporterTypes.TestCase {
return result;
}
_appendFailedTestResult(): reporterTypes.TestResult {
const result: reporterTypes.TestResult = {
retry: this.results.length,
parallelIndex: -1,
workerIndex: -1,
duration: 0,
startTime: new Date(),
stdout: [],
stderr: [],
attachments: [],
status: 'failed',
steps: [],
errors: [{ message: 'dependency test failed' }]
};
this.results.push(result);
return result;
}
_grepTitle() {
const path: string[] = [];
this.parent._collectGrepTitlePath(path);

View file

@ -592,6 +592,7 @@ export const baseFullConfig: reporterTypes.FullConfig = {
configFile: '',
rootDir: '',
quiet: false,
failDependentTests: false,
shard: null,
updateSnapshots: 'missing',
updateSourceMethod: 'patch',

View file

@ -295,6 +295,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid
maxFailures: options.x ? 1 : (options.maxFailures ? parseInt(options.maxFailures, 10) : undefined),
outputDir: options.output ? path.resolve(process.cwd(), options.output) : undefined,
quiet: options.quiet ? options.quiet : undefined,
failDependentTests: options.failDependentTests ? options.failDependentTests : undefined,
repeatEach: options.repeatEach ? parseInt(options.repeatEach, 10) : undefined,
retries: options.retries ? parseInt(options.retries, 10) : undefined,
reporter: resolveReporterOption(options.reporter),
@ -375,6 +376,7 @@ const testOptions: [string, string][] = [
['--pass-with-no-tests', `Makes test run succeed even if no tests were found`],
['--project <project-name...>', `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)`],
['--quiet', `Suppress stdio`],
['--failDependentTests', `fail all `],
['--repeat-each <N>', `Run each test N times (default: 1)`],
['--reporter <reporter>', `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${defaultReporter}")`],
['--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`],

View file

@ -355,7 +355,7 @@ function createRunTestsTask(): Task<TestRun> {
// We don't want to run the test groups belonging to the projects
// that depend on the projects that failed previously.
const phaseTestGroups: TestGroup[] = [];
for (const { project, testGroups } of projects) {
for (const { project, testGroups, projectSuite } of projects) {
// Inherit extra environment variables from dependencies.
let extraEnv: Record<string, string | undefined> = {};
for (const dep of project.deps)
@ -367,8 +367,9 @@ function createRunTestsTask(): Task<TestRun> {
const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
if (!hasFailedDeps)
phaseTestGroups.push(...testGroups);
else if (project.fullConfig.config.failDependentTests)
projectSuite.allTests().forEach(test => test._appendFailedTestResult());
}
if (phaseTestGroups.length) {
await dispatcher!.run(phaseTestGroups, extraEnvByProjectId);
await dispatcher.stop();

View file

@ -1332,6 +1332,23 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
*/
quiet?: boolean;
/**
* Whether to automatically fail dependency tests after parent test fails
*
* **Usage**
*
* ```js
* // playwright.config.ts
* import { defineConfig } from '@playwright/test';
*
* export default defineConfig({
* failDependentTests: true
* });
* ```
*
*/
failDependentTests?: boolean;
/**
* The number of times to repeat each test, useful for debugging flaky tests.
*
@ -1805,6 +1822,8 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
*/
quiet: boolean;
failDependentTests: boolean;
/**
* See [testConfig.reportSlowTests](https://playwright.dev/docs/api/class-testconfig#test-config-report-slow-tests).
*/

View file

@ -894,3 +894,30 @@ test('page.pause() should disable test timeout', async ({ runInlineTest }) => {
expect(result.passed).toBe(1);
expect(result.output).toContain('success!');
});
test('should automatically fail tests when their dependency tests fail', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
failDependentTests: true,
projects: [ { testMatch: /dependent\.test\.ts/, dependencies: ['a'] }, { name: 'a', testMatch: /setup\.test\.ts/ } ]
}
`,
'setup.test.ts': `
import { test, expect } from '@playwright/test';
test('fail', async ({ page }) => {
test.expect(1).toBeFalsy();
});
`,
'dependent.test.ts': `
import { test, expect } from '@playwright/test';
test('pass', async ({ page }) => {
test.expect(0).toBeFalsy();
});
`
}, { workers: 1 });
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.failed).toBe(2);
expect(result.skipped).toBe(0);
});