codeceptjs 4.0.0-beta.1 → 4.0.0-beta.11.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.
Files changed (207) 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 +71 -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 +238 -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 +300 -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 +124 -50
  39. package/lib/container.js +765 -260
  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 +47 -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/mask_data.js +47 -0
  157. package/lib/utils.js +411 -228
  158. package/lib/workerStorage.js +37 -34
  159. package/lib/workers.js +532 -296
  160. package/package.json +115 -95
  161. package/translations/de-DE.js +5 -3
  162. package/translations/fr-FR.js +5 -4
  163. package/translations/index.js +22 -12
  164. package/translations/it-IT.js +4 -3
  165. package/translations/ja-JP.js +4 -3
  166. package/translations/nl-NL.js +76 -0
  167. package/translations/pl-PL.js +4 -3
  168. package/translations/pt-BR.js +4 -3
  169. package/translations/ru-RU.js +4 -3
  170. package/translations/utils.js +10 -0
  171. package/translations/zh-CN.js +4 -3
  172. package/translations/zh-TW.js +4 -3
  173. package/typings/index.d.ts +546 -185
  174. package/typings/promiseBasedTypes.d.ts +150 -879
  175. package/typings/types.d.ts +547 -996
  176. package/lib/cli.js +0 -249
  177. package/lib/dirname.js +0 -5
  178. package/lib/helper/Expect.js +0 -425
  179. package/lib/helper/ExpectHelper.js +0 -399
  180. package/lib/helper/MockServer.js +0 -223
  181. package/lib/helper/Nightmare.js +0 -1411
  182. package/lib/helper/Protractor.js +0 -1835
  183. package/lib/helper/SoftExpectHelper.js +0 -381
  184. package/lib/helper/TestCafe.js +0 -1410
  185. package/lib/helper/clientscripts/nightmare.js +0 -213
  186. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  187. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  188. package/lib/interfaces/bdd.js +0 -98
  189. package/lib/interfaces/featureConfig.js +0 -69
  190. package/lib/interfaces/gherkin.js +0 -195
  191. package/lib/listener/artifacts.js +0 -19
  192. package/lib/listener/retry.js +0 -68
  193. package/lib/listener/timeout.js +0 -109
  194. package/lib/mochaFactory.js +0 -110
  195. package/lib/plugin/allure.js +0 -15
  196. package/lib/plugin/commentStep.js +0 -136
  197. package/lib/plugin/debugErrors.js +0 -67
  198. package/lib/plugin/eachElement.js +0 -127
  199. package/lib/plugin/fakerTransform.js +0 -49
  200. package/lib/plugin/retryTo.js +0 -121
  201. package/lib/plugin/selenoid.js +0 -371
  202. package/lib/plugin/standardActingHelpers.js +0 -9
  203. package/lib/plugin/tryTo.js +0 -105
  204. package/lib/plugin/wdio.js +0 -246
  205. package/lib/scenario.js +0 -222
  206. package/lib/ui.js +0 -238
  207. package/lib/within.js +0 -70
package/lib/codecept.js CHANGED
@@ -1,228 +1,304 @@
1
- import { existsSync, readFileSync } from 'fs';
2
- import glob from 'glob';
3
- import { resolve, dirname, isAbsolute, join } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { createRequire } from 'node:module';
6
- import container from './container.js';
7
- import Config from './config.js';
8
- import * as event from './event.js';
9
- import runHook from './hooks.js';
10
- import * as output from './output.js';
11
- import { emptyFolder } from './utils.js';
12
- import * as index from './index.js';
13
-
14
- // Helpers and features
15
- import * as actor from './actor.js';
16
- import pause from './pause.js';
17
- import within from './within.js';
18
- import session from './session.js';
19
- import data from './data/table.js';
20
- import Locator from './locator.js';
21
- import secret from './secret.js';
22
- import * as stepDefinitions from './interfaces/bdd.js';
23
-
24
- // Listeners
25
- import listener from './listener/steps.js';
26
- import listenerArtifacts from './listener/artifacts.js';
27
- import listenerConfig from './listener/config.js';
28
- import listenerHelpers from './listener/helpers.js';
29
- import listenerRetry from './listener/retry.js';
30
- import listenerTimeout from './listener/timeout.js';
31
- import listenerExit from './listener/exit.js';
32
-
33
- const __dirname = dirname(fileURLToPath(import.meta.url));
34
- const require = createRequire(import.meta.url);
1
+ import { existsSync, readFileSync } from 'fs'
2
+ import { globSync } from 'glob'
3
+ import shuffle from 'lodash.shuffle'
4
+ import fsPath from 'path'
5
+ import { resolve } from 'path'
6
+ import { fileURLToPath } from 'url'
7
+ import { dirname } from 'path'
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = dirname(__filename)
11
+
12
+ import Helper from '@codeceptjs/helper'
13
+ import container from './container.js'
14
+ import Config from './config.js'
15
+ import event from './event.js'
16
+ import runHook from './hooks.js'
17
+ import ActorFactory from './actor.js'
18
+ import output from './output.js'
19
+ import { emptyFolder } from './utils.js'
20
+ import { initCodeceptGlobals } from './globals.js'
21
+ import recorder from './recorder.js'
22
+
23
+ import storeListener from './listener/store.js'
24
+ import stepsListener from './listener/steps.js'
25
+ import configListener from './listener/config.js'
26
+ import resultListener from './listener/result.js'
27
+ import helpersListener from './listener/helpers.js'
28
+ import globalTimeoutListener from './listener/globalTimeout.js'
29
+ import globalRetryListener from './listener/globalRetry.js'
30
+ import exitListener from './listener/exit.js'
31
+ import emptyRunListener from './listener/emptyRun.js'
35
32
 
36
33
  /**
37
- * CodeceptJS runner class.
34
+ * CodeceptJS runner
38
35
  */
39
- export default class Codecept {
36
+ class Codecept {
40
37
  /**
41
- * Initializes CodeceptJS runner with config and options.
42
- * @param {Object} config - Configuration object.
43
- * @param {Object} opts - Options.
38
+ * Create CodeceptJS runner.
39
+ * Config and options should be passed
40
+ *
41
+ * @param {*} config
42
+ * @param {*} opts
44
43
  */
45
44
  constructor(config, opts) {
46
- this.config = Config.create(config);
47
- this.opts = opts;
48
- this.testFiles = [];
49
- this.requireModules(config?.require);
45
+ this.config = Config.create(config)
46
+ this.opts = opts
47
+ this.testFiles = new Array(0)
48
+ this.requiringModules = config.require
50
49
  }
51
50
 
52
51
  /**
53
- * Requires necessary modules before running CodeceptJS.
54
- * @param {string[]} requiringModules - List of modules to require.
52
+ * Require modules before codeceptjs running
53
+ *
54
+ * @param {string[]} requiringModules
55
55
  */
56
- requireModules(requiringModules) {
57
- requiringModules?.forEach((requiredModule) => {
58
- const isLocalFile = existsSync(requiredModule) || existsSync(`${requiredModule}.js`);
59
- const modulePath = isLocalFile ? resolve(requiredModule) : requiredModule;
60
- require(modulePath);
61
- });
56
+ async requireModules(requiringModules) {
57
+ if (requiringModules) {
58
+ for (const requiredModule of requiringModules) {
59
+ let modulePath = requiredModule
60
+ const isLocalFile = existsSync(modulePath) || existsSync(`${modulePath}.js`)
61
+ if (isLocalFile) {
62
+ modulePath = resolve(modulePath)
63
+ // For ESM, ensure .js extension for local files
64
+ if (!modulePath.endsWith('.js') && !modulePath.endsWith('.mjs') && !modulePath.endsWith('.cjs')) {
65
+ if (existsSync(`${modulePath}.js`)) {
66
+ modulePath = `${modulePath}.js`
67
+ }
68
+ }
69
+ }
70
+ // Use dynamic import for ESM
71
+ await import(modulePath)
72
+ }
73
+ }
62
74
  }
63
75
 
64
76
  /**
65
- * Initializes CodeceptJS in a specific directory.
66
- * @param {string} dir - Directory path.
77
+ * Initialize CodeceptJS at specific dir.
78
+ * Loads config, requires factory methods
79
+ *
80
+ * @param {string} dir
67
81
  */
68
- init(dir) {
69
- this.initGlobals(dir);
70
- container.create(this.config, this.opts);
71
- this.runHooks();
82
+ async init(dir) {
83
+ await this.initGlobals(dir)
84
+ // Require modules before initializing
85
+ await this.requireModules(this.requiringModules)
86
+ // initializing listeners
87
+ await container.create(this.config, this.opts)
88
+ // Store container globally for easy access
89
+ global.container = container
90
+ await this.runHooks()
72
91
  }
73
92
 
74
93
  /**
75
- * Initializes global variables.
76
- * @param {string} dir - Directory path.
94
+ * Creates global variables
95
+ *
96
+ * @param {string} dir
77
97
  */
78
- initGlobals(dir) {
79
- global.codecept_dir = dir;
80
- global.output_dir = resolve(dir, this.config.output);
81
-
82
- if (this.config.emptyOutputFolder) emptyFolder(global.output_dir);
83
-
84
- if (!this.config.noGlobals) {
85
- this.initGlobalHelpers();
86
- }
98
+ async initGlobals(dir) {
99
+ await initCodeceptGlobals(dir, this.config, container)
87
100
  }
88
101
 
89
102
  /**
90
- * Initializes global helpers and other CodeceptJS features.
103
+ * Executes hooks.
91
104
  */
92
- initGlobalHelpers() {
93
- global.Helper = global.codecept_helper = index.generated;
94
- global.actor = global.codecept_actor = actor;
95
- global.pause = pause;
96
- global.within = within;
97
- global.session = session;
98
- global.DataTable = data;
99
- global.locate = (locator) => new Locator(locator);
100
- global.inject = container.support;
101
- global.share = container.share;
102
- global.secret = secret;
103
- global.codecept_debug = output.debug;
104
- global.codeceptjs = index;
105
-
106
- // BDD
107
- global.Given = stepDefinitions.Given;
108
- global.When = stepDefinitions.When;
109
- global.Then = stepDefinitions.Then;
110
- global.DefineParameterType = stepDefinitions.defineParameterType;
111
- global.debugMode = false;
112
- }
105
+ async runHooks() {
106
+ // default hooks - dynamic imports for ESM
107
+ const listenerModules = [
108
+ './listener/store.js',
109
+ './listener/steps.js',
110
+ './listener/config.js',
111
+ './listener/result.js',
112
+ './listener/helpers.js',
113
+ './listener/globalTimeout.js',
114
+ './listener/globalRetry.js',
115
+ './listener/retryEnhancer.js',
116
+ './listener/exit.js',
117
+ './listener/emptyRun.js',
118
+ ]
113
119
 
114
- /**
115
- * Runs all hooks, including custom and default.
116
- */
117
- runHooks() {
118
- const listeners = [
119
- listener,
120
- listenerArtifacts,
121
- listenerConfig,
122
- listenerHelpers,
123
- listenerRetry,
124
- listenerTimeout,
125
- listenerExit,
126
- ];
127
-
128
- listeners.forEach(runHook);
129
-
130
- // Run custom hooks
131
- this.config.hooks.forEach(runHook);
120
+ for (const modulePath of listenerModules) {
121
+ const module = await import(modulePath)
122
+ runHook(module.default || module)
123
+ }
124
+
125
+ // custom hooks (previous iteration of plugins)
126
+ this.config.hooks.forEach(hook => runHook(hook))
132
127
  }
133
128
 
134
129
  /**
135
- * Executes the bootstrap process.
130
+ * Executes bootstrap.
131
+ *
132
+ * @returns {Promise<void>}
136
133
  */
137
134
  async bootstrap() {
138
- return runHook(this.config.bootstrap, 'bootstrap');
135
+ return runHook(this.config.bootstrap, 'bootstrap')
139
136
  }
140
137
 
141
138
  /**
142
- * Executes the teardown process.
139
+ * Executes teardown.
140
+ *
141
+ * @returns {Promise<void>}
143
142
  */
144
143
  async teardown() {
145
- return runHook(this.config.teardown, 'teardown');
144
+ return runHook(this.config.teardown, 'teardown')
146
145
  }
147
146
 
148
147
  /**
149
- * Loads test files based on the given pattern or config.
150
- * @param {string} [pattern] - Optional pattern for loading tests.
148
+ * Loads tests by pattern or by config.tests
149
+ *
150
+ * @param {string} [pattern]
151
151
  */
152
152
  loadTests(pattern) {
153
- const patterns = this.getTestPatterns(pattern);
154
- const options = { cwd: global.codecept_dir };
153
+ const options = {
154
+ cwd: global.codecept_dir,
155
+ }
155
156
 
156
- patterns.forEach((p) => {
157
- glob.sync(p, options).forEach((file) => {
158
- if (!file.includes('node_modules')) {
159
- const fullPath = isAbsolute(file) ? file : join(global.codecept_dir, file);
160
- const resolvedFile = resolve(fullPath);
157
+ let patterns = [pattern]
158
+ if (!pattern) {
159
+ patterns = []
161
160
 
162
- if (!this.testFiles.includes(resolvedFile)) {
163
- this.testFiles.push(resolvedFile);
164
- }
161
+ // If the user wants to test a specific set of test files as an array or string.
162
+ if (this.config.tests && !this.opts.features) {
163
+ if (Array.isArray(this.config.tests)) {
164
+ patterns.push(...this.config.tests)
165
+ } else {
166
+ patterns.push(this.config.tests)
165
167
  }
166
- });
167
- });
168
+ }
169
+
170
+ if (this.config.gherkin && this.config.gherkin.features && !this.opts.tests) {
171
+ if (Array.isArray(this.config.gherkin.features)) {
172
+ this.config.gherkin.features.forEach(feature => {
173
+ patterns.push(feature)
174
+ })
175
+ } else {
176
+ patterns.push(this.config.gherkin.features)
177
+ }
178
+ }
179
+ }
180
+
181
+ for (pattern of patterns) {
182
+ if (pattern) {
183
+ globSync(pattern, options).forEach(file => {
184
+ if (file.includes('node_modules')) return
185
+ if (!fsPath.isAbsolute(file)) {
186
+ file = fsPath.join(global.codecept_dir, file)
187
+ }
188
+ if (!this.testFiles.includes(fsPath.resolve(file))) {
189
+ this.testFiles.push(fsPath.resolve(file))
190
+ }
191
+ })
192
+ }
193
+ }
194
+
195
+ if (this.opts.shuffle) {
196
+ this.testFiles = shuffle(this.testFiles)
197
+ }
198
+
199
+ if (this.opts.shard) {
200
+ this.testFiles = this._applySharding(this.testFiles, this.opts.shard)
201
+ }
168
202
  }
169
203
 
170
204
  /**
171
- * Gets test patterns based on config and options.
172
- * @param {string} [pattern] - Test pattern to match.
173
- * @returns {string[]} - Array of test patterns.
205
+ * Apply sharding to test files based on shard configuration
206
+ *
207
+ * @param {Array<string>} testFiles - Array of test file paths
208
+ * @param {string} shardConfig - Shard configuration in format "index/total" (e.g., "1/4")
209
+ * @returns {Array<string>} - Filtered array of test files for this shard
174
210
  */
175
- getTestPatterns(pattern) {
176
- if (pattern) return [pattern];
211
+ _applySharding(testFiles, shardConfig) {
212
+ const shardMatch = shardConfig.match(/^(\d+)\/(\d+)$/)
213
+ if (!shardMatch) {
214
+ throw new Error('Invalid shard format. Expected format: "index/total" (e.g., "1/4")')
215
+ }
216
+
217
+ const shardIndex = parseInt(shardMatch[1], 10)
218
+ const shardTotal = parseInt(shardMatch[2], 10)
177
219
 
178
- const patterns = [];
179
- const { tests, gherkin } = this.config;
220
+ if (shardTotal < 1) {
221
+ throw new Error('Shard total must be at least 1')
222
+ }
180
223
 
181
- if (tests && !this.opts.features) {
182
- patterns.push(...(Array.isArray(tests) ? tests : [tests]));
224
+ if (shardIndex < 1 || shardIndex > shardTotal) {
225
+ throw new Error(`Shard index ${shardIndex} must be between 1 and ${shardTotal}`)
183
226
  }
184
227
 
185
- if (gherkin?.features && !this.opts.tests) {
186
- patterns.push(...(Array.isArray(gherkin.features) ? gherkin.features : [gherkin.features]));
228
+ if (testFiles.length === 0) {
229
+ return testFiles
187
230
  }
188
231
 
189
- return patterns;
232
+ // Calculate which tests belong to this shard
233
+ const shardSize = Math.ceil(testFiles.length / shardTotal)
234
+ const startIndex = (shardIndex - 1) * shardSize
235
+ const endIndex = Math.min(startIndex + shardSize, testFiles.length)
236
+
237
+ return testFiles.slice(startIndex, endIndex)
190
238
  }
191
239
 
192
240
  /**
193
- * Runs tests either specific to a file or all loaded tests.
194
- * @param {string} [test] - Test file to run.
241
+ * Run a specific test or all loaded tests.
242
+ *
243
+ * @param {string} [test]
195
244
  * @returns {Promise<void>}
196
245
  */
197
246
  async run(test) {
198
- const mocha = container.mocha();
199
- mocha.files = this.testFiles;
247
+ await container.started()
200
248
 
201
- if (test) {
202
- const testPath = isAbsolute(test) ? test : join(global.codecept_dir, test);
203
- mocha.files = mocha.files.filter((t) => resolve(t) === resolve(testPath));
249
+ // Ensure translations are loaded for Gherkin features
250
+ try {
251
+ const { loadTranslations } = await import('./mocha/gherkin.js')
252
+ await loadTranslations()
253
+ } catch (e) {
254
+ // Ignore if gherkin module not available
204
255
  }
205
256
 
206
257
  return new Promise((resolve, reject) => {
258
+ const mocha = container.mocha()
259
+ mocha.files = this.testFiles
260
+
261
+ if (test) {
262
+ if (!fsPath.isAbsolute(test)) {
263
+ test = fsPath.join(global.codecept_dir, test)
264
+ }
265
+ const testBasename = fsPath.basename(test, '.js')
266
+ const testFeatureBasename = fsPath.basename(test, '.feature')
267
+ mocha.files = mocha.files.filter(t => {
268
+ return fsPath.basename(t, '.js') === testBasename || fsPath.basename(t, '.feature') === testFeatureBasename || t === test
269
+ })
270
+ }
271
+
272
+ const done = async (failures) => {
273
+ event.emit(event.all.result, container.result())
274
+ event.emit(event.all.after, this)
275
+ // Wait for any recorder tasks added by event.all.after handlers
276
+ await recorder.promise()
277
+ // Set exit code based on test failures
278
+ if (failures) {
279
+ process.exitCode = 1
280
+ }
281
+ resolve()
282
+ }
283
+
207
284
  try {
208
- event.emit(event.all.before, this);
209
- mocha.run(() => {
210
- event.emit(event.all.result, this);
211
- event.emit(event.all.after, this);
212
- resolve();
213
- });
214
- } catch (error) {
215
- output.output.error(error.stack);
216
- reject(error);
285
+ event.emit(event.all.before, this)
286
+ mocha.run(async (failures) => await done(failures))
287
+ } catch (e) {
288
+ output.error(e.stack)
289
+ reject(e)
217
290
  }
218
- });
291
+ })
219
292
  }
220
- }
221
293
 
222
- /**
223
- * Retrieves the version from package.json.
224
- * @returns {string} - The version of the package.
225
- */
226
- export function version() {
227
- return JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8')).version;
294
+ /**
295
+ * Returns the version string of CodeceptJS.
296
+ *
297
+ * @returns {string} The version string.
298
+ */
299
+ static version() {
300
+ return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
301
+ }
228
302
  }
303
+
304
+ export default Codecept
package/lib/colorUtils.js CHANGED
@@ -144,14 +144,14 @@ function convertColorNameToHex(color) {
144
144
  whitesmoke: '#f5f5f5',
145
145
  yellow: '#ffff00',
146
146
  yellowgreen: '#9acd32',
147
- };
147
+ }
148
148
 
149
- const cColor = `${color}`.toLowerCase();
149
+ const cColor = `${color}`.toLowerCase()
150
150
  if (typeof colors[cColor] !== 'undefined') {
151
- return colors[cColor];
151
+ return colors[cColor]
152
152
  }
153
153
 
154
- return color;
154
+ return color
155
155
  }
156
156
 
157
157
  /**
@@ -160,23 +160,23 @@ function convertColorNameToHex(color) {
160
160
  */
161
161
  function convertHexColorToRgba(hex) {
162
162
  // Expand shorthand form (e.g. "#03F") to full form (e.g. "#0033FF")
163
- const shorthandRegex = /^#([a-f\d])([a-f\d])([a-f\d])$/i;
163
+ const shorthandRegex = /^#([a-f\d])([a-f\d])([a-f\d])$/i
164
164
  const hexFull = `${hex}`.replace(shorthandRegex, (m, r, g, b) => {
165
- return r + r + g + g + b + b;
166
- });
165
+ return r + r + g + g + b + b
166
+ })
167
167
 
168
- const result = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexFull);
168
+ const result = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexFull)
169
169
  if (!result) {
170
170
  // Return untouched if not a hex code
171
- return hex;
171
+ return hex
172
172
  }
173
173
 
174
- const r = parseInt(result[1], 16);
175
- const g = parseInt(result[2], 16);
176
- const b = parseInt(result[3], 16);
177
- const a = 1;
174
+ const r = parseInt(result[1], 16)
175
+ const g = parseInt(result[2], 16)
176
+ const b = parseInt(result[3], 16)
177
+ const a = 1
178
178
 
179
- return `rgba(${r}, ${g}, ${b}, ${a})`;
179
+ return `rgba(${r}, ${g}, ${b}, ${a})`
180
180
  }
181
181
 
182
182
  /**
@@ -189,30 +189,30 @@ function convertHexColorToRgba(hex) {
189
189
  *
190
190
  * @param {string} color Color as a string, i.e. rgb(85,0,0)
191
191
  */
192
- export function convertColorToRGBA(color) {
193
- const cstr = `${color}`.toLowerCase().trim() || '';
192
+ function convertColorToRGBA(color) {
193
+ const cstr = `${color}`.toLowerCase().trim() || ''
194
194
 
195
195
  if (!/^rgba?\(.+?\)$/.test(cstr)) {
196
196
  // Convert both color names and hex colors to rgba
197
- const hexColor = convertColorNameToHex(color);
198
- return convertHexColorToRgba(hexColor);
197
+ const hexColor = convertColorNameToHex(color)
198
+ return convertHexColorToRgba(hexColor)
199
199
  }
200
200
 
201
201
  // Convert rgb to rgba
202
- const channels = cstr.match(/([\-0-9.]+)/g) || [];
202
+ const channels = cstr.match(/([\-0-9.]+)/g) || []
203
203
 
204
204
  switch (channels.length) {
205
205
  case 3:
206
206
  // Convert rgb to rgba
207
- return `rgba(${channels.join(', ')}, 1)`;
207
+ return `rgba(${channels.join(', ')}, 1)`
208
208
 
209
209
  case 4:
210
210
  // Format rgba
211
- return `rgba(${channels.join(', ')})`;
211
+ return `rgba(${channels.join(', ')})`
212
212
 
213
213
  default:
214
214
  // Unexpected color format. Leave it untouched (let the user handle it)
215
- return color;
215
+ return color
216
216
  }
217
217
  }
218
218
 
@@ -221,35 +221,33 @@ export function convertColorToRGBA(color) {
221
221
  *
222
222
  * @param {string} prop CSS Property name
223
223
  */
224
- export function isColorProperty(prop) {
225
- return [
226
- 'color',
227
- 'background',
228
- 'backgroundColor',
229
- 'background-color',
230
- 'borderColor',
231
- 'border-color',
232
- 'borderBottomColor',
233
- 'border-bottom-color',
234
- 'borderLeftColor',
235
- 'border-left-color',
236
- 'borderRightColor',
237
- 'borderTopColor',
238
- 'caretColor',
239
- 'columnRuleColor',
240
- 'outlineColor',
241
- 'textDecorationColor',
242
- 'border-right-color',
243
- 'border-top-color',
244
- 'caret-color',
245
- 'column-rule-color',
246
- 'outline-color',
247
- 'text-decoration-color',
248
- ].indexOf(prop) > -1;
224
+ function isColorProperty(prop) {
225
+ return (
226
+ [
227
+ 'color',
228
+ 'background',
229
+ 'backgroundColor',
230
+ 'background-color',
231
+ 'borderColor',
232
+ 'border-color',
233
+ 'borderBottomColor',
234
+ 'border-bottom-color',
235
+ 'borderLeftColor',
236
+ 'border-left-color',
237
+ 'borderRightColor',
238
+ 'borderTopColor',
239
+ 'caretColor',
240
+ 'columnRuleColor',
241
+ 'outlineColor',
242
+ 'textDecorationColor',
243
+ 'border-right-color',
244
+ 'border-top-color',
245
+ 'caret-color',
246
+ 'column-rule-color',
247
+ 'outline-color',
248
+ 'text-decoration-color',
249
+ ].indexOf(prop) > -1
250
+ )
249
251
  }
250
252
 
251
- export default {
252
- isColorProperty,
253
- convertColorToRGBA,
254
- convertColorNameToHex,
255
- };
253
+ export { isColorProperty, convertColorToRGBA, convertColorNameToHex }