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/codecept.js CHANGED
@@ -1,228 +1,328 @@
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, pathToFileURL } from 'url'
7
+ import { dirname } from 'path'
8
+ import { createRequire } from 'module'
9
+
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = dirname(__filename)
12
+
13
+ import Helper from '@codeceptjs/helper'
14
+ import container from './container.js'
15
+ import Config from './config.js'
16
+ import event from './event.js'
17
+ import runHook from './hooks.js'
18
+ import ActorFactory from './actor.js'
19
+ import output from './output.js'
20
+ import { emptyFolder } from './utils.js'
21
+ import { initCodeceptGlobals } from './globals.js'
22
+ import { validateTypeScriptSetup } from './utils/loaderCheck.js'
23
+ import recorder from './recorder.js'
24
+
25
+ import storeListener from './listener/store.js'
26
+ import stepsListener from './listener/steps.js'
27
+ import configListener from './listener/config.js'
28
+ import resultListener from './listener/result.js'
29
+ import helpersListener from './listener/helpers.js'
30
+ import globalTimeoutListener from './listener/globalTimeout.js'
31
+ import globalRetryListener from './listener/globalRetry.js'
32
+ import exitListener from './listener/exit.js'
33
+ import emptyRunListener from './listener/emptyRun.js'
35
34
 
36
35
  /**
37
- * CodeceptJS runner class.
36
+ * CodeceptJS runner
38
37
  */
39
- export default class Codecept {
38
+ class Codecept {
40
39
  /**
41
- * Initializes CodeceptJS runner with config and options.
42
- * @param {Object} config - Configuration object.
43
- * @param {Object} opts - Options.
40
+ * Create CodeceptJS runner.
41
+ * Config and options should be passed
42
+ *
43
+ * @param {*} config
44
+ * @param {*} opts
44
45
  */
45
46
  constructor(config, opts) {
46
- this.config = Config.create(config);
47
- this.opts = opts;
48
- this.testFiles = [];
49
- this.requireModules(config?.require);
47
+ this.config = Config.create(config)
48
+ this.opts = opts
49
+ this.testFiles = new Array(0)
50
+ this.requiringModules = config.require
50
51
  }
51
52
 
52
53
  /**
53
- * Requires necessary modules before running CodeceptJS.
54
- * @param {string[]} requiringModules - List of modules to require.
54
+ * Require modules before codeceptjs running
55
+ *
56
+ * @param {string[]} requiringModules
55
57
  */
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
- });
58
+ async requireModules(requiringModules) {
59
+ if (requiringModules) {
60
+ for (const requiredModule of requiringModules) {
61
+ let modulePath = requiredModule
62
+ const isLocalFile = existsSync(modulePath) || existsSync(`${modulePath}.js`)
63
+ if (isLocalFile) {
64
+ modulePath = resolve(modulePath)
65
+ // For ESM, ensure .js extension for local files
66
+ if (!modulePath.endsWith('.js') && !modulePath.endsWith('.mjs') && !modulePath.endsWith('.cjs')) {
67
+ if (existsSync(`${modulePath}.js`)) {
68
+ modulePath = `${modulePath}.js`
69
+ }
70
+ }
71
+ } else {
72
+ // For npm packages, resolve from the user's directory
73
+ // This ensures packages like tsx are found in user's node_modules
74
+ const userDir = global.codecept_dir || process.cwd()
75
+
76
+ try {
77
+ // Use createRequire to resolve from user's directory
78
+ const userRequire = createRequire(pathToFileURL(resolve(userDir, 'package.json')).href)
79
+ const resolvedPath = userRequire.resolve(requiredModule)
80
+ modulePath = pathToFileURL(resolvedPath).href
81
+ } catch (resolveError) {
82
+ // If resolution fails, try direct import (will check from CodeceptJS node_modules)
83
+ // This is the fallback for globally installed packages
84
+ modulePath = requiredModule
85
+ }
86
+ }
87
+ // Use dynamic import for ESM
88
+ await import(modulePath)
89
+ }
90
+ }
62
91
  }
63
92
 
64
93
  /**
65
- * Initializes CodeceptJS in a specific directory.
66
- * @param {string} dir - Directory path.
94
+ * Initialize CodeceptJS at specific dir.
95
+ * Loads config, requires factory methods
96
+ *
97
+ * @param {string} dir
67
98
  */
68
- init(dir) {
69
- this.initGlobals(dir);
70
- container.create(this.config, this.opts);
71
- this.runHooks();
99
+ async init(dir) {
100
+ await this.initGlobals(dir)
101
+ // Require modules before initializing
102
+ await this.requireModules(this.requiringModules)
103
+ // initializing listeners
104
+ await container.create(this.config, this.opts)
105
+ // Store container globally for easy access
106
+ global.container = container
107
+ await this.runHooks()
72
108
  }
73
109
 
74
110
  /**
75
- * Initializes global variables.
76
- * @param {string} dir - Directory path.
111
+ * Creates global variables
112
+ *
113
+ * @param {string} dir
77
114
  */
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
- }
115
+ async initGlobals(dir) {
116
+ await initCodeceptGlobals(dir, this.config, container)
87
117
  }
88
118
 
89
119
  /**
90
- * Initializes global helpers and other CodeceptJS features.
120
+ * Executes hooks.
91
121
  */
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
- }
122
+ async runHooks() {
123
+ // default hooks - dynamic imports for ESM
124
+ const listenerModules = [
125
+ './listener/store.js',
126
+ './listener/steps.js',
127
+ './listener/config.js',
128
+ './listener/result.js',
129
+ './listener/helpers.js',
130
+ './listener/globalTimeout.js',
131
+ './listener/globalRetry.js',
132
+ './listener/retryEnhancer.js',
133
+ './listener/exit.js',
134
+ './listener/emptyRun.js',
135
+ ]
113
136
 
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);
137
+ for (const modulePath of listenerModules) {
138
+ const module = await import(modulePath)
139
+ runHook(module.default || module)
140
+ }
141
+
142
+ // custom hooks (previous iteration of plugins)
143
+ this.config.hooks.forEach(hook => runHook(hook))
132
144
  }
133
145
 
134
146
  /**
135
- * Executes the bootstrap process.
147
+ * Executes bootstrap.
148
+ *
149
+ * @returns {Promise<void>}
136
150
  */
137
151
  async bootstrap() {
138
- return runHook(this.config.bootstrap, 'bootstrap');
152
+ return runHook(this.config.bootstrap, 'bootstrap')
139
153
  }
140
154
 
141
155
  /**
142
- * Executes the teardown process.
156
+ * Executes teardown.
157
+ *
158
+ * @returns {Promise<void>}
143
159
  */
144
160
  async teardown() {
145
- return runHook(this.config.teardown, 'teardown');
161
+ return runHook(this.config.teardown, 'teardown')
146
162
  }
147
163
 
148
164
  /**
149
- * Loads test files based on the given pattern or config.
150
- * @param {string} [pattern] - Optional pattern for loading tests.
165
+ * Loads tests by pattern or by config.tests
166
+ *
167
+ * @param {string} [pattern]
151
168
  */
152
169
  loadTests(pattern) {
153
- const patterns = this.getTestPatterns(pattern);
154
- const options = { cwd: global.codecept_dir };
170
+ const options = {
171
+ cwd: global.codecept_dir,
172
+ }
155
173
 
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);
174
+ let patterns = [pattern]
175
+ if (!pattern) {
176
+ patterns = []
161
177
 
162
- if (!this.testFiles.includes(resolvedFile)) {
163
- this.testFiles.push(resolvedFile);
164
- }
178
+ // If the user wants to test a specific set of test files as an array or string.
179
+ if (this.config.tests && !this.opts.features) {
180
+ if (Array.isArray(this.config.tests)) {
181
+ patterns.push(...this.config.tests)
182
+ } else {
183
+ patterns.push(this.config.tests)
165
184
  }
166
- });
167
- });
185
+ }
186
+
187
+ if (this.config.gherkin && this.config.gherkin.features && !this.opts.tests) {
188
+ if (Array.isArray(this.config.gherkin.features)) {
189
+ this.config.gherkin.features.forEach(feature => {
190
+ patterns.push(feature)
191
+ })
192
+ } else {
193
+ patterns.push(this.config.gherkin.features)
194
+ }
195
+ }
196
+ }
197
+
198
+ for (pattern of patterns) {
199
+ if (pattern) {
200
+ globSync(pattern, options).forEach(file => {
201
+ if (file.includes('node_modules')) return
202
+ if (!fsPath.isAbsolute(file)) {
203
+ file = fsPath.join(global.codecept_dir, file)
204
+ }
205
+ if (!this.testFiles.includes(fsPath.resolve(file))) {
206
+ this.testFiles.push(fsPath.resolve(file))
207
+ }
208
+ })
209
+ }
210
+ }
211
+
212
+ if (this.opts.shuffle) {
213
+ this.testFiles = shuffle(this.testFiles)
214
+ }
215
+
216
+ if (this.opts.shard) {
217
+ this.testFiles = this._applySharding(this.testFiles, this.opts.shard)
218
+ }
168
219
  }
169
220
 
170
221
  /**
171
- * Gets test patterns based on config and options.
172
- * @param {string} [pattern] - Test pattern to match.
173
- * @returns {string[]} - Array of test patterns.
222
+ * Apply sharding to test files based on shard configuration
223
+ *
224
+ * @param {Array<string>} testFiles - Array of test file paths
225
+ * @param {string} shardConfig - Shard configuration in format "index/total" (e.g., "1/4")
226
+ * @returns {Array<string>} - Filtered array of test files for this shard
174
227
  */
175
- getTestPatterns(pattern) {
176
- if (pattern) return [pattern];
228
+ _applySharding(testFiles, shardConfig) {
229
+ const shardMatch = shardConfig.match(/^(\d+)\/(\d+)$/)
230
+ if (!shardMatch) {
231
+ throw new Error('Invalid shard format. Expected format: "index/total" (e.g., "1/4")')
232
+ }
233
+
234
+ const shardIndex = parseInt(shardMatch[1], 10)
235
+ const shardTotal = parseInt(shardMatch[2], 10)
177
236
 
178
- const patterns = [];
179
- const { tests, gherkin } = this.config;
237
+ if (shardTotal < 1) {
238
+ throw new Error('Shard total must be at least 1')
239
+ }
180
240
 
181
- if (tests && !this.opts.features) {
182
- patterns.push(...(Array.isArray(tests) ? tests : [tests]));
241
+ if (shardIndex < 1 || shardIndex > shardTotal) {
242
+ throw new Error(`Shard index ${shardIndex} must be between 1 and ${shardTotal}`)
183
243
  }
184
244
 
185
- if (gherkin?.features && !this.opts.tests) {
186
- patterns.push(...(Array.isArray(gherkin.features) ? gherkin.features : [gherkin.features]));
245
+ if (testFiles.length === 0) {
246
+ return testFiles
187
247
  }
188
248
 
189
- return patterns;
249
+ // Calculate which tests belong to this shard
250
+ const shardSize = Math.ceil(testFiles.length / shardTotal)
251
+ const startIndex = (shardIndex - 1) * shardSize
252
+ const endIndex = Math.min(startIndex + shardSize, testFiles.length)
253
+
254
+ return testFiles.slice(startIndex, endIndex)
190
255
  }
191
256
 
192
257
  /**
193
- * Runs tests either specific to a file or all loaded tests.
194
- * @param {string} [test] - Test file to run.
258
+ * Run a specific test or all loaded tests.
259
+ *
260
+ * @param {string} [test]
195
261
  * @returns {Promise<void>}
196
262
  */
197
263
  async run(test) {
198
- const mocha = container.mocha();
199
- mocha.files = this.testFiles;
264
+ await container.started()
200
265
 
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));
266
+ // Check TypeScript loader configuration before running tests
267
+ const tsValidation = validateTypeScriptSetup(this.testFiles, this.requiringModules || [])
268
+ if (tsValidation.hasError) {
269
+ output.error(tsValidation.message)
270
+ process.exit(1)
271
+ }
272
+
273
+ // Ensure translations are loaded for Gherkin features
274
+ try {
275
+ const { loadTranslations } = await import('./mocha/gherkin.js')
276
+ await loadTranslations()
277
+ } catch (e) {
278
+ // Ignore if gherkin module not available
204
279
  }
205
280
 
206
281
  return new Promise((resolve, reject) => {
282
+ const mocha = container.mocha()
283
+ mocha.files = this.testFiles
284
+
285
+ if (test) {
286
+ if (!fsPath.isAbsolute(test)) {
287
+ test = fsPath.join(global.codecept_dir, test)
288
+ }
289
+ const testBasename = fsPath.basename(test, '.js')
290
+ const testFeatureBasename = fsPath.basename(test, '.feature')
291
+ mocha.files = mocha.files.filter(t => {
292
+ return fsPath.basename(t, '.js') === testBasename || fsPath.basename(t, '.feature') === testFeatureBasename || t === test
293
+ })
294
+ }
295
+
296
+ const done = async (failures) => {
297
+ event.emit(event.all.result, container.result())
298
+ event.emit(event.all.after, this)
299
+ // Wait for any recorder tasks added by event.all.after handlers
300
+ await recorder.promise()
301
+ // Set exit code based on test failures
302
+ if (failures) {
303
+ process.exitCode = 1
304
+ }
305
+ resolve()
306
+ }
307
+
207
308
  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);
309
+ event.emit(event.all.before, this)
310
+ mocha.run(async (failures) => await done(failures))
311
+ } catch (e) {
312
+ output.error(e.stack)
313
+ reject(e)
217
314
  }
218
- });
315
+ })
219
316
  }
220
- }
221
317
 
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;
318
+ /**
319
+ * Returns the version string of CodeceptJS.
320
+ *
321
+ * @returns {string} The version string.
322
+ */
323
+ static version() {
324
+ return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
325
+ }
228
326
  }
327
+
328
+ 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 }