From 3cea17abb6c8d7371a6d4dd913f714c427ea0fa0 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 16 Apr 2024 09:49:11 -0700 Subject: [PATCH] fix: shut down workers before reporter.onEnd (#30329) --- docs/src/test-api/class-testconfig.md | 2 +- packages/playwright/src/runner/taskRunner.ts | 11 +++-- packages/playwright/types/test.d.ts | 3 +- tests/playwright-test/runner.spec.ts | 44 ++++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 0df43c6d0a..a89236cbf0 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -148,7 +148,7 @@ export default defineConfig({ * since: v1.10 - type: ?<[int]> -Maximum time in milliseconds the whole test suite can run. Zero timeout (default) disables this behavior. Useful on CI to prevent broken setup from running too long and wasting resources. Learn more about [various timeouts](../test-timeouts.md). +Maximum time in milliseconds the whole test suite can run. Zero timeout (default) disables this behavior. Useful on CI to prevent broken setup from running too long and wasting resources. Note that even if global timeout is reached, Playwright will still allow up to 30 extra seconds for teardown hooks to finish. Learn more about [various timeouts](../test-timeouts.md). **Usage** diff --git a/packages/playwright/src/runner/taskRunner.ts b/packages/playwright/src/runner/taskRunner.ts index 78352ca5aa..5312cb2f7e 100644 --- a/packages/playwright/src/runner/taskRunner.ts +++ b/packages/playwright/src/runner/taskRunner.ts @@ -105,9 +105,14 @@ export class TaskRunner { status = 'failed'; } cancelPromise?.resolve(); - // Note that upon hitting deadline, we "run cleanup", but it exits immediately - // because of the same deadline. Essentially, we're not performing any cleanup. - const cleanup = () => teardownRunner.runDeferCleanup(context, deadline).then(r => r.status); + const cleanup = async () => { + // Upon hitting deadline we add extra 30s to actually perform cleanup, otherwise + // the task exits immediately because of the same deadline and we may continue + // while the test workers are still running. + const extraTime = timeoutWatcher.timedOut() ? 30_000 : 0; + const { status } = await teardownRunner.runDeferCleanup(context, deadline + extraTime); + return status; + }; return { status, cleanup }; } } diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 7883711476..fe74d00184 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1060,7 +1060,8 @@ interface TestConfig { /** * Maximum time in milliseconds the whole test suite can run. Zero timeout (default) disables this behavior. Useful on - * CI to prevent broken setup from running too long and wasting resources. Learn more about + * CI to prevent broken setup from running too long and wasting resources. Note that even if global timeout is + * reached, Playwright will still allow up to 30 extra seconds for teardown hooks to finish. Learn more about * [various timeouts](https://playwright.dev/docs/test-timeouts). * * **Usage** diff --git a/tests/playwright-test/runner.spec.ts b/tests/playwright-test/runner.spec.ts index bc157158e3..113318faac 100644 --- a/tests/playwright-test/runner.spec.ts +++ b/tests/playwright-test/runner.spec.ts @@ -774,3 +774,47 @@ test('unhandled exception in test.fail should restart worker and continue', asyn expect(result.failed).toBe(0); expect(result.outputLines).toEqual(['bad running worker=0', 'good running worker=1']); }); + +test('wait for workers to finish before reporter.onEnd', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + globalTimeout: 2000, + fullyParallel: true, + reporter: './reporter' + } + `, + 'reporter.ts': ` + export default class MyReporter { + onTestEnd(test) { + console.log('MyReporter.onTestEnd', test.title); + } + onEnd(status) { + console.log('MyReporter.onEnd'); + } + async onExit() { + console.log('MyReporter.onExit'); + } + } + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('first', async ({ }) => { + await new Promise(() => {}); + }); + test('second', async ({ }) => { + expect(1).toBe(2); + }); + `, + }, { workers: 2 }); + expect(result.exitCode).toBe(1); + expect(result.passed).toBe(0); + const endIndex = result.output.indexOf('MyReporter.onEnd'); + expect(endIndex).not.toBe(-1); + const firstIndex = result.output.indexOf('MyReporter.onTestEnd first'); + expect(firstIndex).not.toBe(-1); + expect(firstIndex).toBeLessThan(endIndex); + const secondIndex = result.output.indexOf('MyReporter.onTestEnd second'); + expect(secondIndex).not.toBe(-1); + expect(secondIndex).toBeLessThan(endIndex); +});