codeceptjs 4.0.0-beta.2 → 4.0.0-beta.21

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.
Files changed (209) hide show
  1. package/README.md +133 -120
  2. package/bin/codecept.js +107 -96
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/docs/webapi/click.mustache +5 -1
  6. package/lib/actor.js +73 -103
  7. package/lib/ai.js +159 -188
  8. package/lib/assert/empty.js +22 -24
  9. package/lib/assert/equal.js +30 -37
  10. package/lib/assert/error.js +14 -14
  11. package/lib/assert/include.js +43 -48
  12. package/lib/assert/throws.js +11 -11
  13. package/lib/assert/truth.js +22 -22
  14. package/lib/assert.js +20 -18
  15. package/lib/codecept.js +262 -162
  16. package/lib/colorUtils.js +50 -52
  17. package/lib/command/check.js +206 -0
  18. package/lib/command/configMigrate.js +56 -51
  19. package/lib/command/definitions.js +96 -109
  20. package/lib/command/dryRun.js +77 -79
  21. package/lib/command/generate.js +234 -194
  22. package/lib/command/gherkin/init.js +42 -33
  23. package/lib/command/gherkin/snippets.js +76 -74
  24. package/lib/command/gherkin/steps.js +20 -17
  25. package/lib/command/info.js +74 -38
  26. package/lib/command/init.js +301 -290
  27. package/lib/command/interactive.js +41 -32
  28. package/lib/command/list.js +28 -27
  29. package/lib/command/run-multiple/chunk.js +51 -48
  30. package/lib/command/run-multiple/collection.js +5 -5
  31. package/lib/command/run-multiple/run.js +5 -1
  32. package/lib/command/run-multiple.js +97 -97
  33. package/lib/command/run-rerun.js +19 -25
  34. package/lib/command/run-workers.js +68 -92
  35. package/lib/command/run.js +39 -27
  36. package/lib/command/utils.js +80 -64
  37. package/lib/command/workers/runTests.js +388 -226
  38. package/lib/config.js +109 -50
  39. package/lib/container.js +765 -261
  40. package/lib/data/context.js +60 -61
  41. package/lib/data/dataScenarioConfig.js +47 -47
  42. package/lib/data/dataTableArgument.js +32 -32
  43. package/lib/data/table.js +22 -22
  44. package/lib/effects.js +307 -0
  45. package/lib/element/WebElement.js +327 -0
  46. package/lib/els.js +160 -0
  47. package/lib/event.js +173 -163
  48. package/lib/globals.js +141 -0
  49. package/lib/heal.js +89 -85
  50. package/lib/helper/AI.js +131 -41
  51. package/lib/helper/ApiDataFactory.js +107 -75
  52. package/lib/helper/Appium.js +542 -404
  53. package/lib/helper/FileSystem.js +100 -79
  54. package/lib/helper/GraphQL.js +44 -43
  55. package/lib/helper/GraphQLDataFactory.js +52 -52
  56. package/lib/helper/JSONResponse.js +126 -88
  57. package/lib/helper/Mochawesome.js +54 -29
  58. package/lib/helper/Playwright.js +2547 -1316
  59. package/lib/helper/Puppeteer.js +1578 -1181
  60. package/lib/helper/REST.js +209 -68
  61. package/lib/helper/WebDriver.js +1482 -1342
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
  71. package/lib/helper/extras/Popup.js +22 -22
  72. package/lib/helper/extras/React.js +27 -28
  73. package/lib/helper/network/actions.js +36 -42
  74. package/lib/helper/network/utils.js +78 -84
  75. package/lib/helper/scripts/blurElement.js +5 -5
  76. package/lib/helper/scripts/focusElement.js +5 -5
  77. package/lib/helper/scripts/highlightElement.js +8 -8
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -3
  80. package/lib/history.js +23 -19
  81. package/lib/hooks.js +8 -8
  82. package/lib/html.js +94 -104
  83. package/lib/index.js +38 -27
  84. package/lib/listener/config.js +30 -23
  85. package/lib/listener/emptyRun.js +54 -0
  86. package/lib/listener/enhancedGlobalRetry.js +110 -0
  87. package/lib/listener/exit.js +16 -18
  88. package/lib/listener/globalRetry.js +70 -0
  89. package/lib/listener/globalTimeout.js +181 -0
  90. package/lib/listener/helpers.js +76 -51
  91. package/lib/listener/mocha.js +10 -11
  92. package/lib/listener/result.js +11 -0
  93. package/lib/listener/retryEnhancer.js +85 -0
  94. package/lib/listener/steps.js +71 -59
  95. package/lib/listener/store.js +20 -0
  96. package/lib/locator.js +214 -197
  97. package/lib/mocha/asyncWrapper.js +274 -0
  98. package/lib/mocha/bdd.js +167 -0
  99. package/lib/mocha/cli.js +341 -0
  100. package/lib/mocha/factory.js +163 -0
  101. package/lib/mocha/featureConfig.js +89 -0
  102. package/lib/mocha/gherkin.js +231 -0
  103. package/lib/mocha/hooks.js +121 -0
  104. package/lib/mocha/index.js +21 -0
  105. package/lib/mocha/inject.js +46 -0
  106. package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
  107. package/lib/mocha/suite.js +89 -0
  108. package/lib/mocha/test.js +184 -0
  109. package/lib/mocha/types.d.ts +42 -0
  110. package/lib/mocha/ui.js +242 -0
  111. package/lib/output.js +141 -71
  112. package/lib/parser.js +54 -44
  113. package/lib/pause.js +173 -145
  114. package/lib/plugin/analyze.js +403 -0
  115. package/lib/plugin/{autoLogin.js → auth.js} +178 -79
  116. package/lib/plugin/autoDelay.js +36 -40
  117. package/lib/plugin/coverage.js +131 -78
  118. package/lib/plugin/customLocator.js +22 -21
  119. package/lib/plugin/customReporter.js +53 -0
  120. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  121. package/lib/plugin/heal.js +101 -110
  122. package/lib/plugin/htmlReporter.js +3648 -0
  123. package/lib/plugin/pageInfo.js +140 -0
  124. package/lib/plugin/pauseOnFail.js +12 -11
  125. package/lib/plugin/retryFailedStep.js +82 -47
  126. package/lib/plugin/screenshotOnFail.js +111 -92
  127. package/lib/plugin/stepByStepReport.js +159 -101
  128. package/lib/plugin/stepTimeout.js +20 -25
  129. package/lib/plugin/subtitles.js +38 -38
  130. package/lib/recorder.js +193 -130
  131. package/lib/rerun.js +94 -49
  132. package/lib/result.js +238 -0
  133. package/lib/retryCoordinator.js +207 -0
  134. package/lib/secret.js +20 -18
  135. package/lib/session.js +95 -89
  136. package/lib/step/base.js +239 -0
  137. package/lib/step/comment.js +10 -0
  138. package/lib/step/config.js +50 -0
  139. package/lib/step/func.js +46 -0
  140. package/lib/step/helper.js +50 -0
  141. package/lib/step/meta.js +99 -0
  142. package/lib/step/record.js +74 -0
  143. package/lib/step/retry.js +11 -0
  144. package/lib/step/section.js +55 -0
  145. package/lib/step.js +18 -329
  146. package/lib/steps.js +54 -0
  147. package/lib/store.js +38 -7
  148. package/lib/template/heal.js +3 -12
  149. package/lib/template/prompts/generatePageObject.js +31 -0
  150. package/lib/template/prompts/healStep.js +13 -0
  151. package/lib/template/prompts/writeStep.js +9 -0
  152. package/lib/test-server.js +334 -0
  153. package/lib/timeout.js +60 -0
  154. package/lib/transform.js +8 -8
  155. package/lib/translation.js +34 -21
  156. package/lib/utils/loaderCheck.js +124 -0
  157. package/lib/utils/mask_data.js +47 -0
  158. package/lib/utils/typescript.js +237 -0
  159. package/lib/utils.js +411 -228
  160. package/lib/workerStorage.js +37 -34
  161. package/lib/workers.js +532 -296
  162. package/package.json +124 -95
  163. package/translations/de-DE.js +5 -3
  164. package/translations/fr-FR.js +5 -4
  165. package/translations/index.js +22 -12
  166. package/translations/it-IT.js +4 -3
  167. package/translations/ja-JP.js +4 -3
  168. package/translations/nl-NL.js +76 -0
  169. package/translations/pl-PL.js +4 -3
  170. package/translations/pt-BR.js +4 -3
  171. package/translations/ru-RU.js +4 -3
  172. package/translations/utils.js +10 -0
  173. package/translations/zh-CN.js +4 -3
  174. package/translations/zh-TW.js +4 -3
  175. package/typings/index.d.ts +546 -185
  176. package/typings/promiseBasedTypes.d.ts +150 -875
  177. package/typings/types.d.ts +547 -992
  178. package/lib/cli.js +0 -249
  179. package/lib/dirname.js +0 -5
  180. package/lib/helper/Expect.js +0 -425
  181. package/lib/helper/ExpectHelper.js +0 -399
  182. package/lib/helper/MockServer.js +0 -223
  183. package/lib/helper/Nightmare.js +0 -1411
  184. package/lib/helper/Protractor.js +0 -1835
  185. package/lib/helper/SoftExpectHelper.js +0 -381
  186. package/lib/helper/TestCafe.js +0 -1410
  187. package/lib/helper/clientscripts/nightmare.js +0 -213
  188. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  189. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  190. package/lib/interfaces/bdd.js +0 -98
  191. package/lib/interfaces/featureConfig.js +0 -69
  192. package/lib/interfaces/gherkin.js +0 -195
  193. package/lib/listener/artifacts.js +0 -19
  194. package/lib/listener/retry.js +0 -68
  195. package/lib/listener/timeout.js +0 -109
  196. package/lib/mochaFactory.js +0 -110
  197. package/lib/plugin/allure.js +0 -15
  198. package/lib/plugin/commentStep.js +0 -136
  199. package/lib/plugin/debugErrors.js +0 -67
  200. package/lib/plugin/eachElement.js +0 -127
  201. package/lib/plugin/fakerTransform.js +0 -49
  202. package/lib/plugin/retryTo.js +0 -121
  203. package/lib/plugin/selenoid.js +0 -371
  204. package/lib/plugin/standardActingHelpers.js +0 -9
  205. package/lib/plugin/tryTo.js +0 -105
  206. package/lib/plugin/wdio.js +0 -246
  207. package/lib/scenario.js +0 -222
  208. package/lib/ui.js +0 -238
  209. package/lib/within.js +0 -70
package/lib/workers.js CHANGED
@@ -1,263 +1,326 @@
1
- /* eslint-disable max-classes-per-file */
2
- import path from 'path';
3
- import mkdirp from 'mkdirp';
4
- import { Worker } from 'worker_threads';
5
-
6
- import { Suite, Test } from 'mocha';
7
- import { EventEmitter } from 'events';
8
- import ms from 'ms';
9
- import Codecept from './codecept.js';
10
- import { MochaFactory } from './mochaFactory.js';
11
- import Container from './container.js';
12
- import { getTestRoot } from './command/utils.js';
13
- import {
14
- deepClone, fileExists, isFunction, replaceValueDeep,
15
- } from './utils.js';
16
- import mainConfig from './config.js';
17
- import * as output from './output.js';
18
- import * as event from './event.js';
19
- import recorder from './recorder.js';
20
- import runHook from './hooks.js';
21
- import * as WorkerStorage from './workerStorage.js';
22
- import collection from './command/run-multiple/collection.js';
23
-
24
- const __dirname = path.resolve('.', 'lib');
25
- const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js');
26
-
27
- const initializeCodecept = (configPath, options = {}) => {
28
- const codecept = new Codecept(mainConfig.load(configPath || '.'), options);
29
- codecept.init(getTestRoot(configPath));
30
- codecept.loadTests();
31
-
32
- return codecept;
33
- };
34
-
35
- const createOutputDir = (configPath) => {
36
- const config = mainConfig.load(configPath || '.');
37
- const testRoot = getTestRoot(configPath);
38
- const outputDir = path.isAbsolute(config.output) ? config.output : path.join(testRoot, config.output);
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'
25
+
26
+ const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js')
27
+
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))
32
+ codecept.loadTests()
33
+
34
+ return codecept
35
+ }
36
+
37
+ const createOutputDir = async configPath => {
38
+ const config = await mainConfig.load(configPath || '.')
39
+ const testRoot = getTestRoot(configPath)
40
+ const outputDir = path.isAbsolute(config.output) ? config.output : path.join(testRoot, config.output)
39
41
 
40
42
  if (!fileExists(outputDir)) {
41
- output.print(`creating output directory: ${outputDir}`);
42
- mkdirp.sync(outputDir);
43
+ output.print(`creating output directory: ${outputDir}`)
44
+ mkdirp.sync(outputDir)
43
45
  }
44
- };
46
+ }
45
47
 
46
- export function populateGroups(numberOfWorkers) {
47
- const groups = [];
48
+ const populateGroups = numberOfWorkers => {
49
+ const groups = []
48
50
  for (let i = 0; i < numberOfWorkers; i++) {
49
- groups[i] = [];
51
+ groups[i] = []
50
52
  }
51
53
 
52
- return groups;
54
+ return groups
53
55
  }
54
56
 
55
- const createWorker = (workerObject) => {
57
+ const createWorker = (workerObject, isPoolMode = false) => {
56
58
  const worker = new Worker(pathToWorker, {
57
59
  workerData: {
58
60
  options: simplifyObject(workerObject.options),
59
61
  tests: workerObject.tests,
60
62
  testRoot: workerObject.testRoot,
61
63
  workerIndex: workerObject.workerIndex + 1,
64
+ poolMode: isPoolMode,
62
65
  },
63
- });
64
- worker.on('error', err => output.output.error(`Worker Error: ${err.stack}`));
65
-
66
- // @ts-ignore
67
- WorkerStorage.addWorker(worker);
68
- return worker;
69
- };
66
+ stdout: true,
67
+ stderr: true,
68
+ })
69
+
70
+ // Pipe worker stdout/stderr to main process
71
+ if (worker.stdout) {
72
+ worker.stdout.setEncoding('utf8')
73
+ worker.stdout.on('data', (data) => {
74
+ process.stdout.write(data)
75
+ })
76
+ }
77
+ if (worker.stderr) {
78
+ worker.stderr.setEncoding('utf8')
79
+ worker.stderr.on('data', (data) => {
80
+ process.stderr.write(data)
81
+ })
82
+ }
83
+
84
+ worker.on('error', err => {
85
+ console.error(`[Main] Worker Error:`, err)
86
+ output.error(`Worker Error: ${err.stack}`)
87
+ })
88
+
89
+ WorkerStorage.addWorker(worker)
90
+ return worker
91
+ }
70
92
 
71
- const simplifyObject = (object) => {
93
+ const simplifyObject = object => {
72
94
  return Object.keys(object)
73
- .filter((k) => k.indexOf('_') !== 0)
74
- .filter((k) => typeof object[k] !== 'function')
75
- .filter((k) => typeof object[k] !== 'object')
95
+ .filter(k => k.indexOf('_') !== 0)
96
+ .filter(k => typeof object[k] !== 'function')
97
+ .filter(k => typeof object[k] !== 'object')
76
98
  .reduce((obj, key) => {
77
- obj[key] = object[key];
78
- return obj;
79
- }, {});
80
- };
81
-
82
- const repackTest = (test) => {
83
- test = Object.assign(new Test(test.title || '', () => { }), test);
84
- test.parent = Object.assign(new Suite(test.parent.title), test.parent);
85
- return test;
86
- };
99
+ obj[key] = object[key]
100
+ return obj
101
+ }, {})
102
+ }
87
103
 
88
104
  const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns) => {
89
- selectedRuns = options && options.all && config.multiple ? Object.keys(config.multiple) : selectedRuns;
105
+ selectedRuns = options && options.all && config.multiple ? Object.keys(config.multiple) : selectedRuns
90
106
  if (selectedRuns === undefined || !selectedRuns.length || config.multiple === undefined) {
91
107
  return testGroups.map((tests, index) => {
92
- const workerObj = new WorkerObject(index);
93
- workerObj.addConfig(config);
94
- workerObj.addTests(tests);
95
- workerObj.setTestRoot(testRoot);
96
- workerObj.addOptions(options);
97
- return workerObj;
98
- });
108
+ const workerObj = new WorkerObject(index)
109
+ workerObj.addConfig(config)
110
+ workerObj.addTests(tests)
111
+ workerObj.setTestRoot(testRoot)
112
+ workerObj.addOptions(options)
113
+ return workerObj
114
+ })
99
115
  }
100
- const workersToExecute = [];
116
+ const workersToExecute = []
101
117
 
102
- const currentOutputFolder = config.output;
103
- let currentMochawesomeReportDir;
104
- let currentMochaJunitReporterFile;
118
+ const currentOutputFolder = config.output
119
+ let currentMochawesomeReportDir
120
+ let currentMochaJunitReporterFile
105
121
 
106
122
  if (config.mocha && config.mocha.reporterOptions) {
107
- currentMochawesomeReportDir = config.mocha.reporterOptions?.mochawesome.options.reportDir;
108
- currentMochaJunitReporterFile = config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile;
123
+ currentMochawesomeReportDir = config.mocha.reporterOptions?.mochawesome.options.reportDir
124
+ currentMochaJunitReporterFile = config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile
109
125
  }
110
126
 
111
- collection.createRuns(selectedRuns, config).forEach((worker) => {
112
- const separator = path.sep;
113
- const _config = { ...config };
114
- let workerName = worker.name.replace(':', '_');
115
- _config.output = `${currentOutputFolder}${separator}${workerName}`;
127
+ createRuns(selectedRuns, config).forEach(worker => {
128
+ const separator = path.sep
129
+ const _config = { ...config }
130
+ let workerName = worker.name.replace(':', '_')
131
+ _config.output = `${currentOutputFolder}${separator}${workerName}`
116
132
  if (config.mocha && config.mocha.reporterOptions) {
117
- _config.mocha.reporterOptions.mochawesome.options.reportDir = `${currentMochawesomeReportDir}${separator}${workerName}`;
118
-
119
- const _tempArray = currentMochaJunitReporterFile.split(separator);
120
- _tempArray.splice(_tempArray.findIndex(item => item.includes('.xml')), 0, workerName);
121
- _config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile = _tempArray.join(separator);
133
+ _config.mocha.reporterOptions.mochawesome.options.reportDir = `${currentMochawesomeReportDir}${separator}${workerName}`
134
+
135
+ const _tempArray = currentMochaJunitReporterFile.split(separator)
136
+ _tempArray.splice(
137
+ _tempArray.findIndex(item => item.includes('.xml')),
138
+ 0,
139
+ workerName,
140
+ )
141
+ _config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile = _tempArray.join(separator)
122
142
  }
123
- workerName = worker.getOriginalName() || worker.getName();
124
- const workerConfig = worker.getConfig();
125
- workersToExecute.push(getOverridenConfig(workerName, workerConfig, _config));
126
- });
127
- const workers = [];
128
- let index = 0;
129
- testGroups.forEach((tests) => {
130
- const testWorkerArray = [];
131
- workersToExecute.forEach((finalConfig) => {
132
- const workerObj = new WorkerObject(index++);
133
- workerObj.addConfig(finalConfig);
134
- workerObj.addTests(tests);
135
- workerObj.setTestRoot(testRoot);
136
- workerObj.addOptions(options);
137
- testWorkerArray.push(workerObj);
138
- });
139
- workers.push(...testWorkerArray);
140
- });
141
- return workers;
142
- };
143
-
144
- const indexOfSmallestElement = (groups) => {
145
- let i = 0;
143
+ workerName = worker.getOriginalName() || worker.getName()
144
+ const workerConfig = worker.getConfig()
145
+ workersToExecute.push(getOverridenConfig(workerName, workerConfig, _config))
146
+ })
147
+ const workers = []
148
+ let index = 0
149
+ testGroups.forEach(tests => {
150
+ const testWorkerArray = []
151
+ workersToExecute.forEach(finalConfig => {
152
+ const workerObj = new WorkerObject(index++)
153
+ workerObj.addConfig(finalConfig)
154
+ workerObj.addTests(tests)
155
+ workerObj.setTestRoot(testRoot)
156
+ workerObj.addOptions(options)
157
+ testWorkerArray.push(workerObj)
158
+ })
159
+ workers.push(...testWorkerArray)
160
+ })
161
+ return workers
162
+ }
163
+
164
+ const indexOfSmallestElement = groups => {
165
+ let i = 0
146
166
  for (let j = 1; j < groups.length; j++) {
147
167
  if (groups[j - 1].length > groups[j].length) {
148
- i = j;
168
+ i = j
149
169
  }
150
170
  }
151
- return i;
152
- };
171
+ return i
172
+ }
153
173
 
154
- const convertToMochaTests = (testGroup) => {
155
- const group = [];
174
+ const convertToMochaTests = testGroup => {
175
+ const group = []
156
176
  if (testGroup instanceof Array) {
157
- const mocha = MochaFactory.create({}, {});
158
- mocha.files = testGroup;
159
- mocha.loadFiles();
160
- mocha.suite.eachTest((test) => {
161
- group.push(test.uid);
162
- });
163
- mocha.unloadFiles();
177
+ const mocha = MochaFactory.create({}, {})
178
+ mocha.files = testGroup
179
+ mocha.loadFiles()
180
+ mocha.suite.eachTest(test => {
181
+ group.push(test.uid)
182
+ })
183
+ mocha.unloadFiles()
164
184
  }
165
185
 
166
- return group;
167
- };
186
+ return group
187
+ }
168
188
 
169
189
  const getOverridenConfig = (workerName, workerConfig, config) => {
170
190
  // clone config
171
- const overriddenConfig = deepClone(config);
191
+ const overriddenConfig = deepClone(config)
172
192
 
173
193
  // get configuration
174
- const browserConfig = workerConfig.browser;
194
+ const browserConfig = workerConfig.browser
175
195
 
176
196
  for (const key in browserConfig) {
177
- overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key]);
197
+ overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key])
178
198
  }
179
199
 
180
200
  // override tests configuration
181
201
  if (overriddenConfig.tests) {
182
- overriddenConfig.tests = workerConfig.tests;
202
+ overriddenConfig.tests = workerConfig.tests
183
203
  }
184
204
 
185
205
  if (overriddenConfig.gherkin && workerConfig.gherkin && workerConfig.gherkin.features) {
186
- overriddenConfig.gherkin.features = workerConfig.gherkin.features;
206
+ overriddenConfig.gherkin.features = workerConfig.gherkin.features
187
207
  }
188
- return overriddenConfig;
189
- };
208
+ return overriddenConfig
209
+ }
190
210
 
191
211
  class WorkerObject {
192
212
  /**
193
213
  * @param {Number} workerIndex - Unique ID for worker
194
214
  */
195
215
  constructor(workerIndex) {
196
- this.workerIndex = workerIndex;
197
- this.options = {};
198
- this.tests = [];
199
- this.testRoot = getTestRoot();
216
+ this.workerIndex = workerIndex
217
+ this.options = {}
218
+ this.tests = []
219
+ this.testRoot = getTestRoot()
200
220
  }
201
221
 
202
222
  addConfig(config) {
203
- const oldConfig = JSON.parse(this.options.override || '{}');
223
+ const oldConfig = JSON.parse(this.options.override || '{}')
224
+
225
+ // Remove customLocatorStrategies from both old and new config before JSON serialization
226
+ // since functions cannot be serialized and will be lost, causing workers to have empty strategies
227
+ const configWithoutFunctions = { ...config }
228
+
229
+ // Clean both old and new config
230
+ const cleanConfig = (cfg) => {
231
+ if (cfg.helpers) {
232
+ cfg.helpers = { ...cfg.helpers }
233
+ Object.keys(cfg.helpers).forEach(helperName => {
234
+ if (cfg.helpers[helperName] && cfg.helpers[helperName].customLocatorStrategies !== undefined) {
235
+ cfg.helpers[helperName] = { ...cfg.helpers[helperName] }
236
+ delete cfg.helpers[helperName].customLocatorStrategies
237
+ }
238
+ })
239
+ }
240
+ return cfg
241
+ }
242
+
243
+ const cleanedOldConfig = cleanConfig(oldConfig)
244
+ const cleanedNewConfig = cleanConfig(configWithoutFunctions)
245
+
204
246
  const newConfig = {
205
- ...oldConfig,
206
- ...config,
207
- };
208
- this.options.override = JSON.stringify(newConfig);
247
+ ...cleanedOldConfig,
248
+ ...cleanedNewConfig,
249
+ }
250
+ this.options.override = JSON.stringify(newConfig)
209
251
  }
210
252
 
211
253
  addTestFiles(testGroup) {
212
- this.addTests(convertToMochaTests(testGroup));
254
+ this.addTests(convertToMochaTests(testGroup))
213
255
  }
214
256
 
215
257
  addTests(tests) {
216
- this.tests = this.tests.concat(tests);
258
+ this.tests = this.tests.concat(tests)
217
259
  }
218
260
 
219
261
  setTestRoot(path) {
220
- this.testRoot = getTestRoot(path);
262
+ this.testRoot = getTestRoot(path)
221
263
  }
222
264
 
223
265
  addOptions(opts) {
224
266
  this.options = {
225
267
  ...this.options,
226
268
  ...opts,
227
- };
269
+ }
228
270
  }
229
271
  }
230
272
 
231
- export class Workers extends EventEmitter {
273
+ class Workers extends EventEmitter {
232
274
  /**
233
275
  * @param {Number} numberOfWorkers
234
276
  * @param {Object} config
235
277
  */
236
278
  constructor(numberOfWorkers, config = { by: 'test' }) {
237
- super();
238
- this.setMaxListeners(50);
239
- this.codecept = initializeCodecept(config.testConfig, config.options);
240
- this.failuresLog = [];
241
- this.errors = [];
242
- this.numberOfWorkers = 0;
243
- this.closedWorkers = 0;
244
- this.workers = [];
245
- this.stats = {
246
- passes: 0,
247
- failures: 0,
248
- tests: 0,
249
- pending: 0,
250
- };
251
- this.testGroups = [];
252
-
253
- createOutputDir(config.testConfig);
254
- if (numberOfWorkers) this._initWorkers(numberOfWorkers, config);
279
+ super()
280
+ this.setMaxListeners(50)
281
+ this.codeceptPromise = initializeCodecept(config.testConfig, config.options)
282
+ this.codecept = null
283
+ this.config = config // Save config
284
+ this.numberOfWorkersRequested = numberOfWorkers // Save requested worker count
285
+ this.options = config.options || {}
286
+ this.errors = []
287
+ this.numberOfWorkers = 0
288
+ this.closedWorkers = 0
289
+ this.workers = []
290
+ this.testGroups = []
291
+ this.testPool = []
292
+ this.testPoolInitialized = false
293
+ this.isPoolMode = config.by === 'pool'
294
+ this.activeWorkers = new Map()
295
+ this.maxWorkers = numberOfWorkers // Track original worker count for pool mode
296
+
297
+ createOutputDir(config.testConfig)
298
+ // Defer worker initialization until codecept is ready
299
+ }
300
+
301
+ async _ensureInitialized() {
302
+ if (!this.codecept) {
303
+ this.codecept = await this.codeceptPromise
304
+ // Initialize workers in these cases:
305
+ // 1. Positive number requested AND no manual workers pre-spawned
306
+ // 2. Function-based grouping (indicated by negative number) AND no manual workers pre-spawned
307
+ const shouldAutoInit = this.workers.length === 0 && (
308
+ (Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) ||
309
+ (this.numberOfWorkersRequested < 0 && isFunction(this.config.by))
310
+ )
311
+
312
+ if (shouldAutoInit) {
313
+ this._initWorkers(this.numberOfWorkersRequested, this.config)
314
+ }
315
+ }
255
316
  }
256
317
 
257
318
  _initWorkers(numberOfWorkers, config) {
258
- this.splitTestsByGroups(numberOfWorkers, config);
259
- this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns);
260
- this.numberOfWorkers = this.workers.length;
319
+ this.splitTestsByGroups(numberOfWorkers, config)
320
+ // For function-based grouping, use the actual number of test groups created
321
+ const actualNumberOfWorkers = isFunction(config.by) ? this.testGroups.length : numberOfWorkers
322
+ this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns)
323
+ this.numberOfWorkers = this.workers.length
261
324
  }
262
325
 
263
326
  /**
@@ -268,22 +331,27 @@ export class Workers extends EventEmitter {
268
331
  *
269
332
  * - `suite`
270
333
  * - `test`
334
+ * - `pool`
271
335
  * - function(numberOfWorkers)
272
336
  *
273
337
  * This method can be overridden for a better split.
274
338
  */
275
339
  splitTestsByGroups(numberOfWorkers, config) {
276
340
  if (isFunction(config.by)) {
277
- const createTests = config.by;
278
- const testGroups = createTests(numberOfWorkers);
341
+ const createTests = config.by
342
+ const testGroups = createTests(numberOfWorkers)
279
343
  if (!(testGroups instanceof Array)) {
280
- throw new Error('Test group should be an array');
344
+ throw new Error('Test group should be an array')
281
345
  }
282
346
  for (const testGroup of testGroups) {
283
- this.testGroups.push(convertToMochaTests(testGroup));
347
+ this.testGroups.push(convertToMochaTests(testGroup))
284
348
  }
285
349
  } else if (typeof numberOfWorkers === 'number' && numberOfWorkers > 0) {
286
- this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers);
350
+ if (config.by === 'pool') {
351
+ this.createTestPool(numberOfWorkers)
352
+ } else {
353
+ this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
354
+ }
287
355
  }
288
356
  }
289
357
 
@@ -293,53 +361,113 @@ export class Workers extends EventEmitter {
293
361
  * @returns {WorkerObject}
294
362
  */
295
363
  spawn() {
296
- const worker = new WorkerObject(this.numberOfWorkers);
297
- this.workers.push(worker);
298
- this.numberOfWorkers += 1;
299
- return worker;
364
+ const worker = new WorkerObject(this.numberOfWorkers)
365
+ this.workers.push(worker)
366
+ this.numberOfWorkers += 1
367
+ return worker
300
368
  }
301
369
 
302
370
  /**
303
371
  * @param {Number} numberOfWorkers
304
372
  */
305
373
  createGroupsOfTests(numberOfWorkers) {
306
- const files = this.codecept.testFiles;
307
- const mocha = Container.mocha();
308
- mocha.files = files;
309
- mocha.loadFiles();
310
-
311
- const groups = populateGroups(numberOfWorkers);
312
- let groupCounter = 0;
313
-
314
- mocha.suite.eachTest((test) => {
315
- const i = groupCounter % groups.length;
374
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
375
+ if (!this.codecept) return populateGroups(numberOfWorkers)
376
+ const files = this.codecept.testFiles
377
+ const mocha = Container.mocha()
378
+ mocha.files = files
379
+ mocha.loadFiles()
380
+
381
+ const groups = populateGroups(numberOfWorkers)
382
+ let groupCounter = 0
383
+
384
+ mocha.suite.eachTest(test => {
385
+ const i = groupCounter % groups.length
316
386
  if (test) {
317
- groups[i].push(test.uid);
318
- groupCounter++;
387
+ groups[i].push(test.uid)
388
+ groupCounter++
319
389
  }
320
- });
321
- return groups;
390
+ })
391
+ return groups
392
+ }
393
+
394
+ /**
395
+ * @param {Number} numberOfWorkers
396
+ */
397
+ createTestPool(numberOfWorkers) {
398
+ // For pool mode, create empty groups for each worker and initialize empty pool
399
+ // Test pool will be populated lazily when getNextTest() is first called
400
+ this.testPool = []
401
+ this.testPoolInitialized = false
402
+ this.testGroups = populateGroups(numberOfWorkers)
403
+ }
404
+
405
+ /**
406
+ * Initialize the test pool if not already done
407
+ * This is called lazily to avoid state pollution issues during construction
408
+ */
409
+ _initializeTestPool() {
410
+ if (this.testPoolInitialized) {
411
+ return
412
+ }
413
+
414
+ // Ensure codecept is initialized
415
+ if (!this.codecept) {
416
+ output.log('Warning: codecept not initialized when initializing test pool')
417
+ this.testPoolInitialized = true
418
+ return
419
+ }
420
+
421
+ const files = this.codecept.testFiles
422
+ if (!files || files.length === 0) {
423
+ this.testPoolInitialized = true
424
+ return
425
+ }
426
+
427
+ // In ESM, test UIDs are not stable across different mocha instances
428
+ // So instead of using UIDs, we distribute test FILES
429
+ // Each file may contain multiple tests
430
+ for (const file of files) {
431
+ this.testPool.push(file)
432
+ }
433
+
434
+ this.testPoolInitialized = true
435
+ }
436
+
437
+ /**
438
+ * Gets the next test from the pool
439
+ * @returns {String|null} test file path or null if no tests available
440
+ */
441
+ getNextTest() {
442
+ // Lazy initialization of test pool on first call
443
+ if (!this.testPoolInitialized) {
444
+ this._initializeTestPool()
445
+ }
446
+
447
+ return this.testPool.shift()
322
448
  }
323
449
 
324
450
  /**
325
451
  * @param {Number} numberOfWorkers
326
452
  */
327
453
  createGroupsOfSuites(numberOfWorkers) {
328
- const files = this.codecept.testFiles;
329
- const groups = populateGroups(numberOfWorkers);
330
-
331
- const mocha = Container.mocha();
332
- mocha.files = files;
333
- mocha.loadFiles();
334
- mocha.suite.suites.forEach((suite) => {
335
- const i = indexOfSmallestElement(groups);
336
- suite.tests.forEach((test) => {
454
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
455
+ if (!this.codecept) return populateGroups(numberOfWorkers)
456
+ const files = this.codecept.testFiles
457
+ const groups = populateGroups(numberOfWorkers)
458
+
459
+ const mocha = Container.mocha()
460
+ mocha.files = files
461
+ mocha.loadFiles()
462
+ mocha.suite.suites.forEach(suite => {
463
+ const i = indexOfSmallestElement(groups)
464
+ suite.tests.forEach(test => {
337
465
  if (test) {
338
- groups[i].push(test.uid);
466
+ groups[i].push(test.uid)
339
467
  }
340
- });
341
- });
342
- return groups;
468
+ })
469
+ })
470
+ return groups
343
471
  }
344
472
 
345
473
  /**
@@ -347,159 +475,267 @@ export class Workers extends EventEmitter {
347
475
  */
348
476
  overrideConfig(config) {
349
477
  for (const worker of this.workers) {
350
- worker.addConfig(config);
478
+ worker.addConfig(config)
351
479
  }
352
480
  }
353
481
 
354
482
  async bootstrapAll() {
355
- return runHook(this.codecept.config.bootstrapAll, 'bootstrapAll');
483
+ await this._ensureInitialized()
484
+ return runHook(this.codecept.config.bootstrapAll, 'bootstrapAll')
356
485
  }
357
486
 
358
487
  async teardownAll() {
359
- return runHook(this.codecept.config.teardownAll, 'teardownAll');
360
- }
361
-
362
- run() {
363
- this.stats.start = new Date();
364
- this.stats.failedHooks = 0
365
- recorder.startUnlessRunning();
366
- event.dispatcher.emit(event.workers.before);
367
- process.env.RUNS_WITH_WORKERS = 'true';
368
- recorder.add('starting workers', () => {
369
- for (const worker of this.workers) {
370
- const workerThread = createWorker(worker);
371
- this._listenWorkerEvents(workerThread);
372
- }
373
- });
488
+ await this._ensureInitialized()
489
+ return runHook(this.codecept.config.teardownAll, 'teardownAll')
490
+ }
491
+
492
+ async run() {
493
+ await this._ensureInitialized()
494
+ recorder.startUnlessRunning()
495
+ event.dispatcher.emit(event.workers.before)
496
+ process.env.RUNS_WITH_WORKERS = 'true'
497
+
498
+ // Create workers and set up message handlers immediately (not in recorder queue)
499
+ // This prevents a race condition where workers start sending messages before handlers are attached
500
+ const workerThreads = []
501
+ for (const worker of this.workers) {
502
+ const workerThread = createWorker(worker, this.isPoolMode)
503
+ this._listenWorkerEvents(workerThread)
504
+ workerThreads.push(workerThread)
505
+ }
506
+
507
+ recorder.add('workers started', () => {
508
+ // Workers are already running, this is just a placeholder step
509
+ })
510
+
374
511
  return new Promise(resolve => {
375
- this.on('end', resolve);
376
- });
512
+ this.on('end', resolve)
513
+ })
377
514
  }
378
515
 
379
516
  /**
380
517
  * @returns {Array<WorkerObject>}
381
518
  */
382
519
  getWorkers() {
383
- return this.workers;
520
+ return this.workers
384
521
  }
385
522
 
386
523
  /**
387
524
  * @returns {Boolean}
388
525
  */
389
526
  isFailed() {
390
- return (this.stats.failures || this.errors.length) > 0;
527
+ return (Container.result().failures.length || this.errors.length) > 0
391
528
  }
392
529
 
393
530
  _listenWorkerEvents(worker) {
394
- worker.on('message', (message) => {
395
- output.output.process(message.workerIndex);
531
+ // Track worker thread for pool mode
532
+ if (this.isPoolMode) {
533
+ this.activeWorkers.set(worker, { available: true, workerIndex: null })
534
+ }
535
+
536
+ worker.on('message', message => {
537
+ output.process(message.workerIndex)
538
+
539
+ // Handle test requests for pool mode
540
+ if (message.type === 'REQUEST_TEST') {
541
+ if (this.isPoolMode) {
542
+ const nextTest = this.getNextTest()
543
+ if (nextTest) {
544
+ worker.postMessage({ type: 'TEST_ASSIGNED', test: nextTest })
545
+ } else {
546
+ worker.postMessage({ type: 'NO_MORE_TESTS' })
547
+ }
548
+ }
549
+ return
550
+ }
396
551
 
397
552
  // deal with events that are not test cycle related
398
553
  if (!message.event) {
399
- return this.emit('message', message);
554
+ return this.emit('message', message)
400
555
  }
401
556
 
402
557
  switch (message.event) {
403
- case event.all.failures:
404
- this.failuresLog = this.failuresLog.concat(message.data.failuresLog);
405
- this._appendStats(message.data.stats);
406
- break;
558
+ case event.all.result:
559
+ // we ensure consistency of result by adding tests in the very end
560
+ // Check if message.data.stats is valid before adding
561
+ if (message.data.stats) {
562
+ Container.result().addStats(message.data.stats)
563
+ }
564
+
565
+ if (message.data.failures) {
566
+ Container.result().addFailures(message.data.failures)
567
+ }
568
+
569
+ if (message.data.tests) {
570
+ message.data.tests.forEach(test => {
571
+ Container.result().addTest(deserializeTest(test))
572
+ })
573
+ }
574
+
575
+ break
407
576
  case event.suite.before:
408
- this.emit(event.suite.before, repackTest(message.data));
409
- break;
410
- case event.hook.failed:
411
- this.emit(event.hook.failed, repackTest(message.data));
412
- this.errors.push(message.data.err);
413
- break;
577
+ this.emit(event.suite.before, deserializeSuite(message.data))
578
+ break
414
579
  case event.test.before:
415
- this.emit(event.test.before, repackTest(message.data));
416
- break;
580
+ this.emit(event.test.before, deserializeTest(message.data))
581
+ break
417
582
  case event.test.started:
418
- this.emit(event.test.started, repackTest(message.data));
419
- break;
583
+ this.emit(event.test.started, deserializeTest(message.data))
584
+ break
420
585
  case event.test.failed:
421
- this.emit(event.test.failed, repackTest(message.data));
422
- break;
586
+ // For hook failures, emit immediately as there won't be a test.finished event
587
+ // Regular test failures are handled via test.finished to support retries
588
+ if (message.data?.hookName) {
589
+ this.emit(event.test.failed, deserializeTest(message.data))
590
+ }
591
+ // Otherwise skip - we'll emit based on finished state
592
+ break
423
593
  case event.test.passed:
424
- this.emit(event.test.passed, repackTest(message.data));
425
- break;
594
+ // Skip individual passed events - we'll emit based on finished state
595
+ break
426
596
  case event.test.skipped:
427
- this.emit(event.test.skipped, repackTest(message.data));
428
- break;
597
+ this.emit(event.test.skipped, deserializeTest(message.data))
598
+ break
429
599
  case event.test.finished:
430
- this.emit(event.test.finished, repackTest(message.data));
431
- break;
600
+ // Handle different types of test completion properly
601
+ {
602
+ const data = message.data
603
+ const uid = data?.uid
604
+ const isFailed = !!data?.err || data?.state === 'failed'
605
+
606
+ if (uid) {
607
+ // Track states for each test UID
608
+ if (!this._testStates) this._testStates = new Map()
609
+
610
+ if (!this._testStates.has(uid)) {
611
+ this._testStates.set(uid, { states: [], lastData: data })
612
+ }
613
+
614
+ const testState = this._testStates.get(uid)
615
+ testState.states.push({ isFailed, data })
616
+ testState.lastData = data
617
+ } else {
618
+ // For tests without UID, emit immediately
619
+ if (isFailed) {
620
+ this.emit(event.test.failed, deserializeTest(data))
621
+ } else {
622
+ this.emit(event.test.passed, deserializeTest(data))
623
+ }
624
+ }
625
+
626
+ this.emit(event.test.finished, deserializeTest(data))
627
+ }
628
+ break
432
629
  case event.test.after:
433
- this.emit(event.test.after, repackTest(message.data));
434
- break;
630
+ this.emit(event.test.after, deserializeTest(message.data))
631
+ break
435
632
  case event.step.finished:
436
- this.emit(event.step.finished, message.data);
437
- break;
633
+ this.emit(event.step.finished, message.data)
634
+ break
438
635
  case event.step.started:
439
- this.emit(event.step.started, message.data);
440
- break;
636
+ this.emit(event.step.started, message.data)
637
+ break
441
638
  case event.step.passed:
442
- this.emit(event.step.passed, message.data);
443
- break;
639
+ this.emit(event.step.passed, message.data)
640
+ break
444
641
  case event.step.failed:
445
- this.emit(event.step.failed, message.data);
446
- break;
642
+ this.emit(event.step.failed, message.data, message.data.error)
643
+ break
644
+ case event.hook.failed:
645
+ // Hook failures are already reported as test failures by the worker
646
+ // Just emit the hook.failed event for listeners
647
+ this.emit(event.hook.failed, message.data)
648
+ break
447
649
  }
448
- });
650
+ })
449
651
 
450
- worker.on('error', (err) => {
451
- this.errors.push(err);
452
- });
652
+ worker.on('error', err => {
653
+ this.errors.push(err)
654
+ })
453
655
 
454
656
  worker.on('exit', () => {
455
- this.closedWorkers += 1;
456
- if (this.closedWorkers === this.numberOfWorkers) {
457
- this._finishRun();
657
+ this.closedWorkers += 1
658
+
659
+ if (this.isPoolMode) {
660
+ // Pool mode: finish when all workers have exited and no more tests
661
+ if (this.closedWorkers === this.numberOfWorkers) {
662
+ this._finishRun()
663
+ }
664
+ } else if (this.closedWorkers === this.numberOfWorkers) {
665
+ // Regular mode: finish when all original workers have exited
666
+ this._finishRun()
458
667
  }
459
- });
668
+ })
460
669
  }
461
670
 
462
671
  _finishRun() {
463
- event.dispatcher.emit(event.workers.after);
464
- if (this.isFailed()) {
465
- process.exitCode = 1;
672
+ event.dispatcher.emit(event.workers.after, { tests: this.workers.map(worker => worker.tests) })
673
+ if (Container.result().hasFailed) {
674
+ process.exitCode = 1
466
675
  } else {
467
- process.exitCode = 0;
676
+ process.exitCode = 0
677
+ }
678
+
679
+ // Emit states for all tracked tests before emitting results
680
+ if (this._testStates) {
681
+ for (const [uid, { states, lastData }] of this._testStates) {
682
+ // For tests with retries configured, emit all failures + final success
683
+ // For tests without retries, emit only final state
684
+ const lastState = states[states.length - 1]
685
+
686
+ // Check if this test had retries by looking for failure followed by success
687
+ const hasRetryPattern = states.length > 1 &&
688
+ states.some((s, i) => s.isFailed && i < states.length - 1 && !states[i + 1].isFailed)
689
+
690
+ if (hasRetryPattern) {
691
+ // Emit all intermediate failures and final success for retries
692
+ for (const state of states) {
693
+ if (state.isFailed) {
694
+ this.emit(event.test.failed, deserializeTest(state.data))
695
+ } else {
696
+ this.emit(event.test.passed, deserializeTest(state.data))
697
+ }
698
+ }
699
+ } else {
700
+ // For non-retries (like step failures), emit only the final state
701
+ if (lastState.isFailed) {
702
+ this.emit(event.test.failed, deserializeTest(lastState.data))
703
+ } else {
704
+ this.emit(event.test.passed, deserializeTest(lastState.data))
705
+ }
706
+ }
707
+ }
708
+ this._testStates.clear()
468
709
  }
469
- // removed this.finishedTests because in all /lib only first argument (!this.isFailed()) is used)
470
- this.emit(event.all.result, { status: !this.isFailed(), stats: this.stats });
471
- this.emit('end'); // internal event
472
- }
473
710
 
474
- _appendStats(newStats) {
475
- this.stats.passes += newStats.passes;
476
- this.stats.failures += newStats.failures;
477
- this.stats.tests += newStats.tests;
478
- this.stats.pending += newStats.pending;
479
- this.stats.failedHooks += newStats.failedHooks;
711
+ this.emit(event.all.result, Container.result())
712
+ event.dispatcher.emit(event.workers.result, Container.result())
713
+ this.emit('end') // internal event
480
714
  }
481
715
 
482
716
  printResults() {
483
- this.stats.end = new Date();
484
- this.stats.duration = this.stats.end - this.stats.start;
717
+ const result = Container.result()
718
+ result.finish()
485
719
 
486
720
  // Reset process for logs in main thread
487
- output.output.process(null);
488
- output.print();
721
+ output.process(null)
722
+ output.print()
489
723
 
490
- this.failuresLog = this.failuresLog
724
+ this.failuresLog = result.failures
491
725
  .filter(log => log.length && typeof log[1] === 'number')
492
726
  // mocha/lib/reporters/base.js
493
- .map(([format, num, title, message, stack], i) => [format, i + 1, title, message, stack]);
727
+ .map(([format, num, title, message, stack], i) => [format, i + 1, title, message, stack])
494
728
 
495
729
  if (this.failuresLog.length) {
496
- output.print();
497
- output.print('-- FAILURES:');
498
- this.failuresLog.forEach(log => output.print(...log));
730
+ output.print()
731
+ output.print('-- FAILURES:')
732
+ this.failuresLog.forEach(log => output.print(...log))
499
733
  }
500
734
 
501
- output.result(this.stats.passes, this.stats.failures, this.stats.pending, ms(this.stats.duration), this.stats.failedHooks);
502
- output.result(cts.passes, this.stats.failures, this.stats.pending, ms(this.stats.duration));
503
- process.env.RUNS_WITH_WORKERS = 'false';
735
+ output.result(result.stats?.passes || 0, result.stats?.failures || 0, result.stats?.pending || 0, ms(result.duration), result.stats?.failedHooks || 0)
736
+
737
+ process.env.RUNS_WITH_WORKERS = 'false'
504
738
  }
505
739
  }
740
+
741
+ export default Workers