codeceptjs 4.0.0-beta.5 → 4.0.0-beta.6.esm-aria
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -45
- package/bin/codecept.js +46 -57
- package/lib/actor.js +15 -11
- package/lib/ai.js +6 -5
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +66 -107
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +29 -26
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +34 -31
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +10 -24
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +117 -269
- package/lib/config.js +111 -49
- package/lib/container.js +299 -102
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +1 -1
- package/lib/helper/ApiDataFactory.js +16 -13
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +1 -1
- package/lib/helper/JSONResponse.js +19 -30
- package/lib/helper/Mochawesome.js +9 -28
- package/lib/helper/Playwright.js +668 -265
- package/lib/helper/Puppeteer.js +284 -169
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +191 -71
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +33 -48
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +8 -9
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +11 -10
- package/lib/listener/helpers.js +33 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/steps.js +7 -18
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +108 -75
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +60 -27
- package/lib/mocha/factory.js +75 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +57 -25
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -13
- package/lib/mocha/ui.js +28 -31
- package/lib/output.js +11 -9
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +12 -8
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +47 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -23
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +6 -6
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +4 -4
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +68 -97
- package/lib/workerStorage.js +16 -17
- package/lib/workers.js +145 -171
- package/package.json +63 -57
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +11 -2
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +7 -18
- package/typings/promiseBasedTypes.d.ts +3769 -5450
- package/typings/types.d.ts +3953 -5778
- package/bin/test-server.js +0 -53
- package/lib/element/WebElement.js +0 -327
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/listener/retryEnhancer.js +0 -85
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/htmlReporter.js +0 -1947
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/test-server.js +0 -323
- package/lib/within.js +0 -90
package/lib/workerStorage.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { isMainThread, parentPort } from 'worker_threads'
|
|
2
2
|
|
|
3
|
-
const workerObjects = {}
|
|
4
|
-
const shareEvent = 'share'
|
|
3
|
+
const workerObjects = {}
|
|
4
|
+
const shareEvent = 'share'
|
|
5
5
|
|
|
6
|
-
const invokeWorkerListeners =
|
|
7
|
-
const { threadId } = workerObj
|
|
8
|
-
workerObj.on('message',
|
|
6
|
+
const invokeWorkerListeners = workerObj => {
|
|
7
|
+
const { threadId } = workerObj
|
|
8
|
+
workerObj.on('message', messageData => {
|
|
9
9
|
if (messageData.event === shareEvent) {
|
|
10
|
-
|
|
11
|
-
Container.share(messageData.data);
|
|
10
|
+
share(messageData.data)
|
|
12
11
|
}
|
|
13
|
-
})
|
|
12
|
+
})
|
|
14
13
|
workerObj.on('exit', () => {
|
|
15
|
-
delete workerObjects[threadId]
|
|
16
|
-
})
|
|
17
|
-
}
|
|
14
|
+
delete workerObjects[threadId]
|
|
15
|
+
})
|
|
16
|
+
}
|
|
18
17
|
|
|
19
18
|
class WorkerStorage {
|
|
20
19
|
/**
|
|
@@ -24,8 +23,8 @@ class WorkerStorage {
|
|
|
24
23
|
* @param {Worker} workerObj
|
|
25
24
|
*/
|
|
26
25
|
static addWorker(workerObj) {
|
|
27
|
-
workerObjects[workerObj.threadId] = workerObj
|
|
28
|
-
invokeWorkerListeners(workerObj)
|
|
26
|
+
workerObjects[workerObj.threadId] = workerObj
|
|
27
|
+
invokeWorkerListeners(workerObj)
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
/**
|
|
@@ -36,12 +35,12 @@ class WorkerStorage {
|
|
|
36
35
|
static share(data) {
|
|
37
36
|
if (isMainThread) {
|
|
38
37
|
for (const workerObj of Object.values(workerObjects)) {
|
|
39
|
-
workerObj.postMessage({ data })
|
|
38
|
+
workerObj.postMessage({ data })
|
|
40
39
|
}
|
|
41
40
|
} else {
|
|
42
|
-
parentPort.postMessage({ data, event: shareEvent })
|
|
41
|
+
parentPort.postMessage({ data, event: shareEvent })
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
export default WorkerStorage
|
package/lib/workers.js
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { fileURLToPath } from 'url'
|
|
3
|
+
import { dirname } from 'path'
|
|
4
|
+
import { mkdirp } from 'mkdirp'
|
|
5
|
+
import { Worker } from 'worker_threads'
|
|
6
|
+
import { EventEmitter } from 'events'
|
|
7
|
+
import ms from 'ms'
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
10
|
+
const __dirname = dirname(__filename)
|
|
11
|
+
import Codecept from './codecept.js'
|
|
12
|
+
import MochaFactory from './mocha/factory.js'
|
|
13
|
+
import Container from './container.js'
|
|
14
|
+
import { getTestRoot } from './command/utils.js'
|
|
15
|
+
import { isFunction, fileExists, replaceValueDeep, deepClone } from './utils.js'
|
|
16
|
+
import mainConfig from './config.js'
|
|
17
|
+
import output from './output.js'
|
|
18
|
+
import event from './event.js'
|
|
19
|
+
import { deserializeTest } from './mocha/test.js'
|
|
20
|
+
import { deserializeSuite } from './mocha/suite.js'
|
|
21
|
+
import recorder from './recorder.js'
|
|
22
|
+
import runHook from './hooks.js'
|
|
23
|
+
import WorkerStorage from './workerStorage.js'
|
|
24
|
+
import { createRuns } from './command/run-multiple/collection.js'
|
|
21
25
|
|
|
22
26
|
const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js')
|
|
23
27
|
|
|
24
|
-
const initializeCodecept = (configPath, options = {}) => {
|
|
25
|
-
const
|
|
26
|
-
codecept
|
|
28
|
+
const initializeCodecept = async (configPath, options = {}) => {
|
|
29
|
+
const config = await mainConfig.load(configPath || '.')
|
|
30
|
+
const codecept = new Codecept(config, options)
|
|
31
|
+
await codecept.init(getTestRoot(configPath))
|
|
27
32
|
codecept.loadTests()
|
|
28
33
|
|
|
29
34
|
return codecept
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
const createOutputDir = configPath => {
|
|
33
|
-
const config = mainConfig.load(configPath || '.')
|
|
37
|
+
const createOutputDir = async configPath => {
|
|
38
|
+
const config = await mainConfig.load(configPath || '.')
|
|
34
39
|
const testRoot = getTestRoot(configPath)
|
|
35
40
|
const outputDir = path.isAbsolute(config.output) ? config.output : path.join(testRoot, config.output)
|
|
36
41
|
|
|
@@ -49,14 +54,13 @@ const populateGroups = numberOfWorkers => {
|
|
|
49
54
|
return groups
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
const createWorker =
|
|
57
|
+
const createWorker = workerObject => {
|
|
53
58
|
const worker = new Worker(pathToWorker, {
|
|
54
59
|
workerData: {
|
|
55
60
|
options: simplifyObject(workerObject.options),
|
|
56
61
|
tests: workerObject.tests,
|
|
57
62
|
testRoot: workerObject.testRoot,
|
|
58
63
|
workerIndex: workerObject.workerIndex + 1,
|
|
59
|
-
poolMode: isPoolMode,
|
|
60
64
|
},
|
|
61
65
|
})
|
|
62
66
|
worker.on('error', err => output.error(`Worker Error: ${err.stack}`))
|
|
@@ -99,7 +103,7 @@ const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns
|
|
|
99
103
|
currentMochaJunitReporterFile = config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
createRuns(selectedRuns, config).forEach(worker => {
|
|
103
107
|
const separator = path.sep
|
|
104
108
|
const _config = { ...config }
|
|
105
109
|
let workerName = worker.name.replace(':', '_')
|
|
@@ -231,25 +235,43 @@ class Workers extends EventEmitter {
|
|
|
231
235
|
constructor(numberOfWorkers, config = { by: 'test' }) {
|
|
232
236
|
super()
|
|
233
237
|
this.setMaxListeners(50)
|
|
234
|
-
this.
|
|
235
|
-
this.
|
|
238
|
+
this.codeceptPromise = initializeCodecept(config.testConfig, config.options)
|
|
239
|
+
this.codecept = null
|
|
236
240
|
this.errors = []
|
|
237
241
|
this.numberOfWorkers = 0
|
|
238
242
|
this.closedWorkers = 0
|
|
239
243
|
this.workers = []
|
|
240
244
|
this.testGroups = []
|
|
241
|
-
this.
|
|
242
|
-
this.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.maxWorkers = numberOfWorkers // Track original worker count for pool mode
|
|
245
|
+
this.config = config
|
|
246
|
+
this.numberOfWorkersRequested = numberOfWorkers
|
|
247
|
+
// Track emitted pass events to avoid double-counting duplicates from retries/race conditions
|
|
248
|
+
this._passedUids = new Set()
|
|
246
249
|
|
|
247
250
|
createOutputDir(config.testConfig)
|
|
248
|
-
|
|
251
|
+
// Defer worker initialization until codecept is ready
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async _ensureInitialized() {
|
|
255
|
+
if (!this.codecept) {
|
|
256
|
+
this.codecept = await this.codeceptPromise
|
|
257
|
+
// Initialize workers in these cases:
|
|
258
|
+
// 1. Positive number requested AND no manual workers pre-spawned
|
|
259
|
+
// 2. Function-based grouping (indicated by negative number) AND no manual workers pre-spawned
|
|
260
|
+
const shouldAutoInit = this.workers.length === 0 && (
|
|
261
|
+
(Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) ||
|
|
262
|
+
(this.numberOfWorkersRequested < 0 && isFunction(this.config.by))
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if (shouldAutoInit) {
|
|
266
|
+
this._initWorkers(this.numberOfWorkersRequested, this.config)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
249
269
|
}
|
|
250
270
|
|
|
251
271
|
_initWorkers(numberOfWorkers, config) {
|
|
252
272
|
this.splitTestsByGroups(numberOfWorkers, config)
|
|
273
|
+
// For function-based grouping, use the actual number of test groups created
|
|
274
|
+
const actualNumberOfWorkers = isFunction(config.by) ? this.testGroups.length : numberOfWorkers
|
|
253
275
|
this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns)
|
|
254
276
|
this.numberOfWorkers = this.workers.length
|
|
255
277
|
}
|
|
@@ -262,7 +284,6 @@ class Workers extends EventEmitter {
|
|
|
262
284
|
*
|
|
263
285
|
* - `suite`
|
|
264
286
|
* - `test`
|
|
265
|
-
* - `pool`
|
|
266
287
|
* - function(numberOfWorkers)
|
|
267
288
|
*
|
|
268
289
|
* This method can be overridden for a better split.
|
|
@@ -278,11 +299,7 @@ class Workers extends EventEmitter {
|
|
|
278
299
|
this.testGroups.push(convertToMochaTests(testGroup))
|
|
279
300
|
}
|
|
280
301
|
} else if (typeof numberOfWorkers === 'number' && numberOfWorkers > 0) {
|
|
281
|
-
|
|
282
|
-
this.createTestPool(numberOfWorkers)
|
|
283
|
-
} else {
|
|
284
|
-
this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
|
|
285
|
-
}
|
|
302
|
+
this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
|
|
286
303
|
}
|
|
287
304
|
}
|
|
288
305
|
|
|
@@ -302,7 +319,9 @@ class Workers extends EventEmitter {
|
|
|
302
319
|
* @param {Number} numberOfWorkers
|
|
303
320
|
*/
|
|
304
321
|
createGroupsOfTests(numberOfWorkers) {
|
|
305
|
-
|
|
322
|
+
// If Codecept isn't initialized yet, return empty groups as a safe fallback
|
|
323
|
+
if (!this.codecept) return populateGroups(numberOfWorkers)
|
|
324
|
+
const files = this.codecept.testFiles
|
|
306
325
|
const mocha = Container.mocha()
|
|
307
326
|
mocha.files = files
|
|
308
327
|
mocha.loadFiles()
|
|
@@ -320,90 +339,13 @@ class Workers extends EventEmitter {
|
|
|
320
339
|
return groups
|
|
321
340
|
}
|
|
322
341
|
|
|
323
|
-
/**
|
|
324
|
-
* @param {Number} numberOfWorkers
|
|
325
|
-
*/
|
|
326
|
-
createTestPool(numberOfWorkers) {
|
|
327
|
-
// For pool mode, create empty groups for each worker and initialize empty pool
|
|
328
|
-
// Test pool will be populated lazily when getNextTest() is first called
|
|
329
|
-
this.testPool = []
|
|
330
|
-
this.testPoolInitialized = false
|
|
331
|
-
this.testGroups = populateGroups(numberOfWorkers)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Initialize the test pool if not already done
|
|
336
|
-
* This is called lazily to avoid state pollution issues during construction
|
|
337
|
-
*/
|
|
338
|
-
_initializeTestPool() {
|
|
339
|
-
if (this.testPoolInitialized) {
|
|
340
|
-
return
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const files = this.codecept.testFiles
|
|
344
|
-
if (!files || files.length === 0) {
|
|
345
|
-
this.testPoolInitialized = true
|
|
346
|
-
return
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const mocha = Container.mocha()
|
|
351
|
-
mocha.files = files
|
|
352
|
-
mocha.loadFiles()
|
|
353
|
-
|
|
354
|
-
mocha.suite.eachTest(test => {
|
|
355
|
-
if (test) {
|
|
356
|
-
this.testPool.push(test.uid)
|
|
357
|
-
}
|
|
358
|
-
})
|
|
359
|
-
} catch (e) {
|
|
360
|
-
// If mocha loading fails due to state pollution, skip
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// If no tests were found, fallback to using createGroupsOfTests approach
|
|
364
|
-
// This works around state pollution issues
|
|
365
|
-
if (this.testPool.length === 0 && files.length > 0) {
|
|
366
|
-
try {
|
|
367
|
-
const testGroups = this.createGroupsOfTests(2) // Use 2 as a default for fallback
|
|
368
|
-
for (const group of testGroups) {
|
|
369
|
-
this.testPool.push(...group)
|
|
370
|
-
}
|
|
371
|
-
} catch (e) {
|
|
372
|
-
// If createGroupsOfTests fails, fallback to simple file names
|
|
373
|
-
for (const file of files) {
|
|
374
|
-
this.testPool.push(`test_${file.replace(/[^a-zA-Z0-9]/g, '_')}`)
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Last resort fallback for unit tests - add dummy test UIDs
|
|
380
|
-
if (this.testPool.length === 0) {
|
|
381
|
-
for (let i = 0; i < Math.min(files.length, 5); i++) {
|
|
382
|
-
this.testPool.push(`dummy_test_${i}_${Date.now()}`)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
this.testPoolInitialized = true
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Gets the next test from the pool
|
|
391
|
-
* @returns {String|null} test uid or null if no tests available
|
|
392
|
-
*/
|
|
393
|
-
getNextTest() {
|
|
394
|
-
// Initialize test pool lazily on first access
|
|
395
|
-
if (!this.testPoolInitialized) {
|
|
396
|
-
this._initializeTestPool()
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return this.testPool.shift() || null
|
|
400
|
-
}
|
|
401
|
-
|
|
402
342
|
/**
|
|
403
343
|
* @param {Number} numberOfWorkers
|
|
404
344
|
*/
|
|
405
345
|
createGroupsOfSuites(numberOfWorkers) {
|
|
406
|
-
|
|
346
|
+
// If Codecept isn't initialized yet, return empty groups as a safe fallback
|
|
347
|
+
if (!this.codecept) return populateGroups(numberOfWorkers)
|
|
348
|
+
const files = this.codecept.testFiles
|
|
407
349
|
const groups = populateGroups(numberOfWorkers)
|
|
408
350
|
|
|
409
351
|
const mocha = Container.mocha()
|
|
@@ -430,20 +372,23 @@ class Workers extends EventEmitter {
|
|
|
430
372
|
}
|
|
431
373
|
|
|
432
374
|
async bootstrapAll() {
|
|
375
|
+
await this._ensureInitialized()
|
|
433
376
|
return runHook(this.codecept.config.bootstrapAll, 'bootstrapAll')
|
|
434
377
|
}
|
|
435
378
|
|
|
436
379
|
async teardownAll() {
|
|
380
|
+
await this._ensureInitialized()
|
|
437
381
|
return runHook(this.codecept.config.teardownAll, 'teardownAll')
|
|
438
382
|
}
|
|
439
383
|
|
|
440
|
-
run() {
|
|
384
|
+
async run() {
|
|
385
|
+
await this._ensureInitialized()
|
|
441
386
|
recorder.startUnlessRunning()
|
|
442
387
|
event.dispatcher.emit(event.workers.before)
|
|
443
388
|
process.env.RUNS_WITH_WORKERS = 'true'
|
|
444
389
|
recorder.add('starting workers', () => {
|
|
445
390
|
for (const worker of this.workers) {
|
|
446
|
-
const workerThread = createWorker(worker
|
|
391
|
+
const workerThread = createWorker(worker)
|
|
447
392
|
this._listenWorkerEvents(workerThread)
|
|
448
393
|
}
|
|
449
394
|
})
|
|
@@ -467,27 +412,9 @@ class Workers extends EventEmitter {
|
|
|
467
412
|
}
|
|
468
413
|
|
|
469
414
|
_listenWorkerEvents(worker) {
|
|
470
|
-
// Track worker thread for pool mode
|
|
471
|
-
if (this.isPoolMode) {
|
|
472
|
-
this.activeWorkers.set(worker, { available: true, workerIndex: null })
|
|
473
|
-
}
|
|
474
|
-
|
|
475
415
|
worker.on('message', message => {
|
|
476
416
|
output.process(message.workerIndex)
|
|
477
417
|
|
|
478
|
-
// Handle test requests for pool mode
|
|
479
|
-
if (message.type === 'REQUEST_TEST') {
|
|
480
|
-
if (this.isPoolMode) {
|
|
481
|
-
const nextTest = this.getNextTest()
|
|
482
|
-
if (nextTest) {
|
|
483
|
-
worker.postMessage({ type: 'TEST_ASSIGNED', test: nextTest })
|
|
484
|
-
} else {
|
|
485
|
-
worker.postMessage({ type: 'NO_MORE_TESTS' })
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return
|
|
489
|
-
}
|
|
490
|
-
|
|
491
418
|
// deal with events that are not test cycle related
|
|
492
419
|
if (!message.event) {
|
|
493
420
|
return this.emit('message', message)
|
|
@@ -496,21 +423,11 @@ class Workers extends EventEmitter {
|
|
|
496
423
|
switch (message.event) {
|
|
497
424
|
case event.all.result:
|
|
498
425
|
// we ensure consistency of result by adding tests in the very end
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if (message.data.failures) {
|
|
505
|
-
Container.result().addFailures(message.data.failures)
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (message.data.tests) {
|
|
509
|
-
message.data.tests.forEach(test => {
|
|
510
|
-
Container.result().addTest(deserializeTest(test))
|
|
511
|
-
})
|
|
512
|
-
}
|
|
513
|
-
|
|
426
|
+
Container.result().addFailures(message.data.failures)
|
|
427
|
+
Container.result().addStats(message.data.stats)
|
|
428
|
+
message.data.tests.forEach(test => {
|
|
429
|
+
Container.result().addTest(deserializeTest(test))
|
|
430
|
+
})
|
|
514
431
|
break
|
|
515
432
|
case event.suite.before:
|
|
516
433
|
this.emit(event.suite.before, deserializeSuite(message.data))
|
|
@@ -522,16 +439,43 @@ class Workers extends EventEmitter {
|
|
|
522
439
|
this.emit(event.test.started, deserializeTest(message.data))
|
|
523
440
|
break
|
|
524
441
|
case event.test.failed:
|
|
525
|
-
|
|
442
|
+
// Skip individual failed events - we'll emit based on finished state
|
|
526
443
|
break
|
|
527
444
|
case event.test.passed:
|
|
528
|
-
|
|
445
|
+
// Skip individual passed events - we'll emit based on finished state
|
|
529
446
|
break
|
|
530
447
|
case event.test.skipped:
|
|
531
448
|
this.emit(event.test.skipped, deserializeTest(message.data))
|
|
532
449
|
break
|
|
533
450
|
case event.test.finished:
|
|
534
|
-
|
|
451
|
+
// Handle different types of test completion properly
|
|
452
|
+
{
|
|
453
|
+
const data = message.data
|
|
454
|
+
const uid = data?.uid
|
|
455
|
+
const isFailed = !!data?.err || data?.state === 'failed'
|
|
456
|
+
|
|
457
|
+
if (uid) {
|
|
458
|
+
// Track states for each test UID
|
|
459
|
+
if (!this._testStates) this._testStates = new Map()
|
|
460
|
+
|
|
461
|
+
if (!this._testStates.has(uid)) {
|
|
462
|
+
this._testStates.set(uid, { states: [], lastData: data })
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const testState = this._testStates.get(uid)
|
|
466
|
+
testState.states.push({ isFailed, data })
|
|
467
|
+
testState.lastData = data
|
|
468
|
+
} else {
|
|
469
|
+
// For tests without UID, emit immediately
|
|
470
|
+
if (isFailed) {
|
|
471
|
+
this.emit(event.test.failed, deserializeTest(data))
|
|
472
|
+
} else {
|
|
473
|
+
this.emit(event.test.passed, deserializeTest(data))
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
this.emit(event.test.finished, deserializeTest(data))
|
|
478
|
+
}
|
|
535
479
|
break
|
|
536
480
|
case event.test.after:
|
|
537
481
|
this.emit(event.test.after, deserializeTest(message.data))
|
|
@@ -548,6 +492,11 @@ class Workers extends EventEmitter {
|
|
|
548
492
|
case event.step.failed:
|
|
549
493
|
this.emit(event.step.failed, message.data, message.data.error)
|
|
550
494
|
break
|
|
495
|
+
case event.hook.failed:
|
|
496
|
+
// Count hook failures as test failures for event counting
|
|
497
|
+
this.emit(event.test.failed, { title: `Hook failure: ${message.data.hookName || 'unknown'}`, err: message.data.error })
|
|
498
|
+
this.emit(event.hook.failed, message.data)
|
|
499
|
+
break
|
|
551
500
|
}
|
|
552
501
|
})
|
|
553
502
|
|
|
@@ -557,14 +506,7 @@ class Workers extends EventEmitter {
|
|
|
557
506
|
|
|
558
507
|
worker.on('exit', () => {
|
|
559
508
|
this.closedWorkers += 1
|
|
560
|
-
|
|
561
|
-
if (this.isPoolMode) {
|
|
562
|
-
// Pool mode: finish when all workers have exited and no more tests
|
|
563
|
-
if (this.closedWorkers === this.numberOfWorkers) {
|
|
564
|
-
this._finishRun()
|
|
565
|
-
}
|
|
566
|
-
} else if (this.closedWorkers === this.numberOfWorkers) {
|
|
567
|
-
// Regular mode: finish when all original workers have exited
|
|
509
|
+
if (this.closedWorkers === this.numberOfWorkers) {
|
|
568
510
|
this._finishRun()
|
|
569
511
|
}
|
|
570
512
|
})
|
|
@@ -578,6 +520,38 @@ class Workers extends EventEmitter {
|
|
|
578
520
|
process.exitCode = 0
|
|
579
521
|
}
|
|
580
522
|
|
|
523
|
+
// Emit states for all tracked tests before emitting results
|
|
524
|
+
if (this._testStates) {
|
|
525
|
+
for (const [uid, { states, lastData }] of this._testStates) {
|
|
526
|
+
// For tests with retries configured, emit all failures + final success
|
|
527
|
+
// For tests without retries, emit only final state
|
|
528
|
+
const lastState = states[states.length - 1]
|
|
529
|
+
|
|
530
|
+
// Check if this test had retries by looking for failure followed by success
|
|
531
|
+
const hasRetryPattern = states.length > 1 &&
|
|
532
|
+
states.some((s, i) => s.isFailed && i < states.length - 1 && !states[i + 1].isFailed)
|
|
533
|
+
|
|
534
|
+
if (hasRetryPattern) {
|
|
535
|
+
// Emit all intermediate failures and final success for retries
|
|
536
|
+
for (const state of states) {
|
|
537
|
+
if (state.isFailed) {
|
|
538
|
+
this.emit(event.test.failed, deserializeTest(state.data))
|
|
539
|
+
} else {
|
|
540
|
+
this.emit(event.test.passed, deserializeTest(state.data))
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
// For non-retries (like step failures), emit only the final state
|
|
545
|
+
if (lastState.isFailed) {
|
|
546
|
+
this.emit(event.test.failed, deserializeTest(lastState.data))
|
|
547
|
+
} else {
|
|
548
|
+
this.emit(event.test.passed, deserializeTest(lastState.data))
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
this._testStates.clear()
|
|
553
|
+
}
|
|
554
|
+
|
|
581
555
|
this.emit(event.all.result, Container.result())
|
|
582
556
|
event.dispatcher.emit(event.workers.result, Container.result())
|
|
583
557
|
this.emit('end') // internal event
|
|
@@ -602,10 +576,10 @@ class Workers extends EventEmitter {
|
|
|
602
576
|
this.failuresLog.forEach(log => output.print(...log))
|
|
603
577
|
}
|
|
604
578
|
|
|
605
|
-
output.result(result.stats
|
|
579
|
+
output.result(result.stats?.passes || 0, result.stats?.failures || 0, result.stats?.pending || 0, ms(result.duration), result.stats?.failedHooks || 0)
|
|
606
580
|
|
|
607
581
|
process.env.RUNS_WITH_WORKERS = 'false'
|
|
608
582
|
}
|
|
609
583
|
}
|
|
610
584
|
|
|
611
|
-
|
|
585
|
+
export default Workers
|