Skip to content

Commit 4bcf174

Browse files
authored
Merge branch '4.x' into fix/mcp-zombie-mocha
2 parents fb091ec + fb28db2 commit 4bcf174

5 files changed

Lines changed: 31 additions & 36 deletions

File tree

lib/command/run-workers.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,5 @@ export default async function (workerCount, selectedRuns, options) {
8787
process.exitCode = 1
8888
} finally {
8989
await workers.teardownAll()
90-
91-
// Force exit if event loop doesn't clear naturally
92-
// This is needed because worker threads may leave handles open
93-
// even after proper cleanup, preventing natural process termination
94-
if (!options.noExit) {
95-
// Use beforeExit to ensure we run after all other exit handlers
96-
// have set the correct exit code
97-
process.once('beforeExit', (code) => {
98-
// Give cleanup a moment to complete, then force exit with the correct code
99-
setTimeout(() => {
100-
process.exit(code || process.exitCode || 0)
101-
}, 100)
102-
})
103-
}
10490
}
10591
}

lib/command/run.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { getConfig, printError, getTestRoot, createOutputDir } from './utils.js'
1+
import { getConfig, printError, getTestRoot, createOutputDir, autoExit } from './utils.js'
22
import Config from '../config.js'
33
import store from '../store.js'
44
import Codecept from '../codecept.js'
5-
import container from '../container.js'
65

76
export default async function (test, options) {
87
// registering options globally to use in config
@@ -43,19 +42,6 @@ export default async function (test, options) {
4342
process.exitCode = 1
4443
} finally {
4544
await codecept.teardown()
46-
47-
// Schedule a delayed exit to prevent process hanging due to browser helper event loops
48-
// Only needed for Playwright/Puppeteer which keep the event loop alive
49-
// Wait 1 second to allow final cleanup and output to complete
50-
if (!process.env.CODECEPT_DISABLE_AUTO_EXIT) {
51-
const helpers = container.helpers()
52-
const hasBrowserHelper = helpers && (helpers.Playwright || helpers.Puppeteer || helpers.WebDriver)
53-
54-
if (hasBrowserHelper) {
55-
setTimeout(() => {
56-
process.exit(process.exitCode || 0)
57-
}, 1000).unref()
58-
}
59-
}
45+
await autoExit()
6046
}
6147
}

lib/command/utils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ export const createOutputDir = (config, testRoot) => {
107107
}
108108
}
109109

110+
export async function autoExit() {
111+
const timeout = parseInt(process.env.CODECEPT_AUTO_EXIT_TIMEOUT, 10)
112+
if (timeout === 0) return
113+
const exitTimeout = timeout || 2000
114+
115+
const { default: container } = await import('../container.js')
116+
const helpers = container.helpers()
117+
if (!helpers || !Object.values(helpers).some(h => typeof h._cleanup === 'function')) return
118+
119+
const { default: recorder } = await import('../recorder.js')
120+
await Promise.race([recorder.promise(), new Promise(resolve => setTimeout(resolve, exitTimeout))])
121+
process.exit(process.exitCode || 0)
122+
}
123+
110124
export const findConfigFile = testsPath => {
111125
const extensions = ['js', 'ts']
112126
for (const ext of extensions) {

lib/plugin/retryFailedStep.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import debugModule from 'debug'
12
import event from '../event.js'
23
import recorder from '../recorder.js'
34
import store from '../store.js'
45

6+
const debug = debugModule('codeceptjs:retryFailedStep')
7+
58
const defaultConfig = {
69
retries: 3,
710
defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
@@ -147,9 +150,7 @@ export default function (config) {
147150
test.opts.conditionalRetries = config.retries
148151
test.opts.stepRetryPriority = stepRetryPriority
149152

150-
if (process.env.DEBUG_RETRY_PLUGIN) {
151-
console.log('[retryFailedStep] applying retries =', config.retries, 'for test', test.title)
152-
}
153+
debug('applying retries = %d for test %s', config.retries, test.title)
153154
recorder.retry(config)
154155
})
155156

lib/workers.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,10 +547,11 @@ class Workers extends EventEmitter {
547547
if (this.isPoolMode) {
548548
this.activeWorkers.set(worker, { available: true, workerIndex: null })
549549
}
550-
550+
551551
// Track last activity time to detect hanging workers
552552
let lastActivity = Date.now()
553553
let currentTest = null
554+
let autoTerminated = false
554555
const workerTimeout = process.env.CODECEPT_WORKER_TIMEOUT ? ms(process.env.CODECEPT_WORKER_TIMEOUT) : ms('5m')
555556

556557
const timeoutChecker = setInterval(() => {
@@ -611,6 +612,13 @@ class Workers extends EventEmitter {
611612
})
612613
}
613614

615+
const exitTimeout = parseInt(process.env.CODECEPT_AUTO_EXIT_TIMEOUT, 10)
616+
if (exitTimeout === 0) break
617+
setTimeout(() => {
618+
autoTerminated = true
619+
worker.terminate()
620+
}, exitTimeout || 2000)
621+
614622
break
615623
case event.suite.before:
616624
{
@@ -741,8 +749,8 @@ class Workers extends EventEmitter {
741749
worker.on('exit', (code) => {
742750
clearInterval(timeoutChecker)
743751
this.closedWorkers += 1
744-
745-
if (code !== 0) {
752+
753+
if (code !== 0 && !autoTerminated) {
746754
console.error(`[Main] Worker exited with code ${code}`)
747755
if (currentTest) {
748756
console.error(`[Main] Last test running: ${currentTest}`)

0 commit comments

Comments
 (0)