diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index f837ddbfe8..c9828aa10d 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -384,6 +384,10 @@ export default defineConfig({ }); ``` +## property: TestConfig.recreateWorkerAfterFailure? +* since: v1.51 +- type: <[boolean]> + ## property: TestConfig.repeatEach * since: v1.10 - type: ?<[int]> diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index 8d213226ff..273a88723b 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -48,6 +48,7 @@ export class FullConfigInternal { readonly plugins: TestRunnerPluginRegistration[]; readonly projects: FullProjectInternal[] = []; readonly singleTSConfigPath?: string; + readonly recreateWorkerAfterFailure: boolean; cliArgs: string[] = []; cliGrep: string | undefined; cliGrepInvert: string | undefined; @@ -85,6 +86,8 @@ export class FullConfigInternal { // so that plugins such as gitCommitInfoPlugin can populate metadata once. userConfig.metadata = userConfig.metadata || {}; + this.recreateWorkerAfterFailure = userConfig.recreateWorkerAfterFailure ?? true; + this.config = { configFile: resolvedConfigFile, rootDir: pathResolve(configDir, userConfig.testDir) || configDir, diff --git a/packages/playwright/src/worker/workerMain.ts b/packages/playwright/src/worker/workerMain.ts index 126d3c8052..c95d8d43f2 100644 --- a/packages/playwright/src/worker/workerMain.ts +++ b/packages/playwright/src/worker/workerMain.ts @@ -396,6 +396,9 @@ export class WorkerMain extends ProcessRunner { // After hooks get an additional timeout. const afterHooksTimeout = calculateMaxTimeout(this._project.project.timeout, testInfo.timeout); const afterHooksSlot = { timeout: afterHooksTimeout, elapsed: 0 }; + + const FAILURE_AND_RECREATE_WORKER = testInfo._isFailure() && this._config.recreateWorkerAfterFailure; + await testInfo._runAsStep({ title: 'After Hooks', category: 'hook' }, async () => { let firstAfterHooksError: Error | undefined; @@ -428,7 +431,7 @@ export class WorkerMain extends ProcessRunner { // In case of failure the worker will be stopped and we have to make sure that afterAll // hooks run before worker fixtures teardown. for (const suite of reversedSuites) { - if (!nextSuites.has(suite) || testInfo._isFailure()) { + if (!nextSuites.has(suite) || FAILURE_AND_RECREATE_WORKER) { try { await this._runAfterAllHooksForSuite(suite, testInfo); } catch (error) { @@ -443,7 +446,7 @@ export class WorkerMain extends ProcessRunner { checkForFloatingPromises('afterAll/afterEach hooks'); - if (testInfo._isFailure()) + if (FAILURE_AND_RECREATE_WORKER) this._isStopped = true; if (this._isStopped) { diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index bdb45169b6..46afd5f8c1 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1400,6 +1400,8 @@ interface TestConfig { */ quiet?: boolean; + recreateWorkerAfterFailure?: boolean; + /** * The number of times to repeat each test, useful for debugging flaky tests. *