Skip to content

[Bug]: Teardown is not called if test failure and max-failures configured #41713

Closed

Description

Version

1.61.0

Steps to reproduce

Clone this repo https://github.com/atavar3z/pw-teardown-bug.git
run npm install
run npx playwright test --max-failures=1

Expected behavior

I expected the teardown not to be skipped and for the teardown log to be written.

Actual behavior

If tests are run with max-failures configured and number of failures is met then the teardown is skipped.

Additional context

Old bug was closed due to lack of upvotes - #33214
However, I believe this is worth fixing.
Global teardown can be used for things like clearing DB. Not executing the teardown when maxFailures is reached may clog up an environment.
Would be great if you consider it again.

Environment

Environment
System:
OS: Windows 11 10.0.22631
CPU: (8) x64 Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
Memory: 1.01 GB / 7.77 GB
Binaries:
Node: 16.13.0
npm: 8.1.0
IDEs:
VSCode: 1.94.2
Languages:
Bash: 5.0.17
npmPackages:
@playwright/test: ^1.61.0 => 1.61.0

Activity

  1. github-actions commented on Jul 9, 2026

    @github-actions
    Contributor

    Hi, I'm the Playwright bot and I took a first look at this.

    Reproduced on tip-of-tree (npm @next, 1.62.0-alpha-1783623505000). When a run is interrupted by --max-failures, a dependency project's teardown project is skipped — the setup runs, the failing test trips the max-failures limit, and the teardown never gets a chance to run. Confirmed against a stripped-down, network-free version of your repro, so this reproduces on the current code, not just 1.61.0. Looks like a real bug.

    Minimal repro (no network, self-contained)

    playwright.config.ts:

    import { defineConfig } from '@playwright/test';
    export default defineConfig({
      projects: [
        { name: 'setup', testMatch: /global\.setup\.ts/, teardown: 'teardown' },
        { name: 'teardown', testMatch: /global\.teardown\.ts/ },
        { name: 'main', testMatch: /example\.spec\.ts/, dependencies: ['setup'] },
      ],
    });

    tests/global.setup.ts:

    import { test as setup } from '@playwright/test';
    setup('setup', async ({}) => { console.log('SETUP-RAN'); });

    tests/global.teardown.ts:

    import { test as teardown } from '@playwright/test';
    teardown('teardown', async ({}) => { console.log('TEARDOWN-RAN'); });

    tests/example.spec.ts:

    import { test, expect } from '@playwright/test';
    test('failing', async ({}) => { expect(1).toBe(2); });
    test('passing', async ({}) => { expect(1).toBe(1); });

    Result:

    $ npx playwright test --max-failures=1
    SETUP-RAN
      1 failed
      1 passed          <-- teardown project never ran
    
    $ npx playwright test
    SETUP-RAN
    TEARDOWN-RAN        <-- teardown runs normally without max-failures
      1 failed
      3 passed
    
    What I tried
    • Cloned the linked repro (atavar3z/pw-teardown-bug) and boiled it down to the network-free config above so the trigger is just: setup project → teardown project, a main project that depends on setup, one failing test, run with --max-failures=1.
    • Ran on tip-of-tree @next (1.62.0-alpha-1783623505000). Teardown is skipped only when the run is interrupted by max-failures; without --max-failures the teardown project runs as expected.
    • This is a test-runner behavior and browser-independent — no browser is launched by the setup/teardown projects.

    Here's a self-contained acceptance test in our test-runner style that captures current behavior (the %%teardown assertion is the desired behavior that fails today):

    import { test, expect } from './playwright-test-fixtures';
    
    test('should run teardown when interrupted by maxFailures', async ({ runInlineTest }) => {
      // https://github.com/microsoft/playwright/issues/41713
      const result = await runInlineTest({
        'playwright.config.ts': `
          module.exports = {
            projects: [
              { name: 'setup', testMatch: '**/setup.ts', teardown: 'teardown' },
              { name: 'teardown', testMatch: '**/teardown.ts' },
              { name: 'project', dependencies: ['setup'], testMatch: '**/a.spec.ts' },
            ],
          };`,
        'setup.ts': `
          import { test } from '@playwright/test';
          test('setup', async ({}) => { console.log('\\n%%setup'); });
        `,
        'teardown.ts': `
          import { test } from '@playwright/test';
          test('teardown', async ({}) => { console.log('\\n%%teardown'); });
        `,
        'a.spec.ts': `
          import { test, expect } from '@playwright/test';
          test('failing', async ({}) => { expect(1).toBe(2); });
        `,
      }, { workers: 1, maxFailures: 1 });
      expect(result.output).toContain('%%setup');
      // Desired: teardown should still run when the run is cut short by maxFailures.
      expect(result.output).toContain('%%teardown');
    });

    This is a first pass, not the final word — the open design question is whether teardown projects should be exempt from the max-failures interruption (same as the earlier report #33214).

  2. pradapjackie commented on Jul 10, 2026

    @pradapjackie
    Contributor

    I'd like to work on this. Root cause: after --max-failures, the next phase dispatcher stops immediately, so project teardowns never run. Fix keeps scheduling teardown projects with ignoreMaxFailures. OK to open a PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions