codeceptjs 4.0.0-beta.1 → 4.0.0-beta.10.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 +751 -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/rerun.js CHANGED
@@ -1,80 +1,125 @@
1
- import fsPath from 'path';
2
- import container from './container.js';
3
- import * as event from './event.js';
4
- import BaseCodecept from './codecept.js';
5
- import * as output from './output.js';
1
+ import fsPath from 'path'
2
+ import container from './container.js'
3
+ import event from './event.js'
4
+ import BaseCodecept from './codecept.js'
5
+ import output from './output.js'
6
+ import { createRequire } from 'module'
7
+
8
+ const require = createRequire(import.meta.url)
6
9
 
7
10
  class CodeceptRerunner extends BaseCodecept {
8
- runOnce(test) {
9
- return new Promise((resolve, reject) => {
10
- // @ts-ignore
11
- container.createMocha();
12
- const mocha = container.mocha();
13
- this.testFiles.forEach((file) => {
14
- // delete require.cache[file];
15
- });
16
- mocha.files = this.testFiles;
17
- if (test) {
18
- if (!fsPath.isAbsolute(test)) {
19
- test = fsPath.join(global.codecept_dir, test);
20
- }
21
- mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
22
- }
11
+ async runOnce(test) {
12
+ await container.started()
13
+
14
+ // Ensure translations are loaded for Gherkin features
15
+ try {
16
+ const { loadTranslations } = await import('./mocha/gherkin.js')
17
+ await loadTranslations()
18
+ } catch (e) {
19
+ // Ignore if gherkin module not available
20
+ }
21
+
22
+ return new Promise(async (resolve, reject) => {
23
23
  try {
24
- mocha.run((failures) => {
25
- if (failures === 0) {
26
- resolve();
24
+ // Create a fresh Mocha instance for each run
25
+ // @ts-ignore
26
+ container.createMocha()
27
+ const mocha = container.mocha()
28
+
29
+ let filesToRun = this.testFiles
30
+ if (test) {
31
+ if (!fsPath.isAbsolute(test)) {
32
+ test = fsPath.join(global.codecept_dir, test)
33
+ }
34
+ filesToRun = this.testFiles.filter(t => fsPath.basename(t, '.js') === test || t === test)
35
+ }
36
+
37
+ // Clear any existing tests/suites
38
+ mocha.suite.suites = []
39
+ mocha.suite.tests = []
40
+
41
+ // Manually load each test file by importing it
42
+ for (const file of filesToRun) {
43
+ try {
44
+ // Clear CommonJS cache if available (for mixed environments)
45
+ try {
46
+ delete require.cache[file]
47
+ } catch (e) {
48
+ // ESM modules don't have require.cache, ignore
49
+ }
50
+
51
+ // Force reload the module by using a cache-busting query parameter
52
+ const fileUrl = `${fsPath.resolve(file)}?t=${Date.now()}`
53
+ await import(fileUrl)
54
+ } catch (e) {
55
+ console.error(`Error loading test file ${file}:`, e)
56
+ }
57
+ }
58
+
59
+ const done = () => {
60
+ event.emit(event.all.result, container.result())
61
+ event.emit(event.all.after, this)
62
+
63
+ // Check if there were any failures
64
+ if (container.result().hasFailed) {
65
+ reject(new Error('Test run failed'))
27
66
  } else {
28
- reject(new Error(`${failures} tests fail`));
67
+ resolve(undefined)
29
68
  }
30
- });
69
+ }
70
+
71
+ event.emit(event.all.before, this)
72
+ mocha.run(() => done())
31
73
  } catch (e) {
32
- reject(e);
74
+ output.error(e.stack)
75
+ reject(e)
33
76
  }
34
- });
77
+ })
35
78
  }
36
79
 
37
80
  async runTests(test) {
38
- const configRerun = this.config.rerun || {};
39
- const minSuccess = configRerun.minSuccess || 1;
40
- const maxReruns = configRerun.maxReruns || 1;
81
+ const configRerun = this.config.rerun || {}
82
+ const minSuccess = configRerun.minSuccess || 1
83
+ const maxReruns = configRerun.maxReruns || 1
41
84
  if (minSuccess > maxReruns) {
42
- process.exitCode = 1;
43
- throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`);
85
+ process.exitCode = 1
86
+ throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
44
87
  }
45
88
  if (maxReruns === 1) {
46
- await this.runOnce(test);
47
- return;
89
+ await this.runOnce(test)
90
+ return
48
91
  }
49
- let successCounter = 0;
50
- let rerunsCounter = 0;
92
+ let successCounter = 0
93
+ let rerunsCounter = 0
51
94
  while (rerunsCounter < maxReruns && successCounter < minSuccess) {
52
- rerunsCounter++;
95
+ container.result().reset() // reset result
96
+ rerunsCounter++
53
97
  try {
54
- await this.runOnce(test);
55
- successCounter++;
56
- output.output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`);
98
+ await this.runOnce(test)
99
+ successCounter++
100
+ output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
57
101
  } catch (e) {
58
- output.output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`);
59
- console.error(e);
102
+ output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
103
+ console.error(e)
60
104
  }
61
105
  }
62
106
  if (successCounter < minSuccess) {
63
- throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`);
107
+ throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
64
108
  }
65
109
  }
66
110
 
67
111
  async run(test) {
68
- event.emit(event.all.before, this);
112
+ event.emit(event.all.before, this)
69
113
  try {
70
- await this.runTests(test);
114
+ await this.runTests(test)
71
115
  } catch (e) {
72
- output.output.error(e.stack);
116
+ output.error(e.stack)
117
+ throw e
73
118
  } finally {
74
- event.emit(event.all.result, this);
75
- event.emit(event.all.after, this);
119
+ event.emit(event.all.result, this)
120
+ event.emit(event.all.after, this)
76
121
  }
77
122
  }
78
123
  }
79
124
 
80
- export default CodeceptRerunner;
125
+ export default CodeceptRerunner
package/lib/result.js ADDED
@@ -0,0 +1,238 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { serializeTest } from './mocha/test.js'
4
+
5
+ /**
6
+ * @typedef {Object} Stats Statistics for a test result.
7
+ * @property {number} passes Number of passed tests.
8
+ * @property {number} failures Number of failed tests.
9
+ * @property {number} tests Total number of tests.
10
+ * @property {number} pending Number of pending tests.
11
+ * @property {number} failedHooks Number of failed hooks.
12
+ * @property {Date} start Start time of the test run.
13
+ * @property {Date} end End time of the test run.
14
+ * @property {number} duration Duration of the test run, in milliseconds.
15
+ */
16
+
17
+ /**
18
+ * Result of a test run. Will be emitted for example in "event.all.result" events.
19
+ */
20
+ class Result {
21
+ constructor() {
22
+ this._startTime = new Date()
23
+ this._endTime = null
24
+
25
+ this.reset()
26
+ this.start()
27
+ }
28
+
29
+ /**
30
+ * Resets all collected stats, tests, and failure reports.
31
+ */
32
+ reset() {
33
+ this._stats = {
34
+ passes: 0,
35
+ failures: 0,
36
+ tests: 0,
37
+ pending: 0,
38
+ failedHooks: 0,
39
+ start: null,
40
+ end: null,
41
+ duration: 0,
42
+ }
43
+
44
+ /**
45
+ * @type {CodeceptJS.Test[]}
46
+ * @private
47
+ */
48
+ this._tests = []
49
+
50
+ /**
51
+ * @type {string[][]}
52
+ * @private
53
+ */
54
+ this._failures = []
55
+ }
56
+
57
+ /**
58
+ * Sets the start time to the current time.
59
+ */
60
+ start() {
61
+ this._startTime = new Date()
62
+ }
63
+
64
+ /**
65
+ * Sets the end time to the current time.
66
+ */
67
+ finish() {
68
+ this._endTime = new Date()
69
+ }
70
+
71
+ /**
72
+ * Whether this result contains any failed tests.
73
+ *
74
+ * @type {boolean}
75
+ * @readonly
76
+ */
77
+ get hasFailed() {
78
+ return this._stats.failures > 0
79
+ }
80
+
81
+ /**
82
+ * All collected tests.
83
+ *
84
+ * @type {CodeceptJS.Test[]}
85
+ * @readonly
86
+ */
87
+ get tests() {
88
+ return this._tests
89
+ }
90
+
91
+ /**
92
+ * The failure reports (array of strings per failed test).
93
+ *
94
+ * @type {string[][]}
95
+ * @readonly
96
+ */
97
+ get failures() {
98
+ return this._failures.filter(f => f && (!Array.isArray(f) || f.length > 0))
99
+ }
100
+
101
+ /**
102
+ * The test statistics.
103
+ *
104
+ * @type {Stats}
105
+ * @readonly
106
+ */
107
+ get stats() {
108
+ return this._stats
109
+ }
110
+
111
+ /**
112
+ * The start time of the test run.
113
+ *
114
+ * @type {Date}
115
+ * @readonly
116
+ */
117
+ get startTime() {
118
+ return this._startTime
119
+ }
120
+
121
+ /**
122
+ * Adds a test to this result.
123
+ *
124
+ * @param {CodeceptJS.Test} test
125
+ */
126
+ addTest(test) {
127
+ const existingTestIndex = this._tests.findIndex(t => !!t.uid && t.uid === test.uid)
128
+ if (existingTestIndex >= 0) {
129
+ this._tests[existingTestIndex] = test
130
+ return
131
+ }
132
+
133
+ this._tests.push(test)
134
+ }
135
+
136
+ /**
137
+ * Adds failure reports to this result.
138
+ *
139
+ * @param {string[][]} newFailures
140
+ */
141
+ addFailures(newFailures) {
142
+ this._failures.push(...newFailures)
143
+ }
144
+
145
+ /**
146
+ * Whether this result contains any failed tests.
147
+ *
148
+ * @type {boolean}
149
+ * @readonly
150
+ */
151
+ get hasFailures() {
152
+ return this.stats.failures > 0
153
+ }
154
+
155
+ /**
156
+ * The duration of the test run, in milliseconds.
157
+ *
158
+ * @type {number}
159
+ * @readonly
160
+ */
161
+ get duration() {
162
+ return this._endTime ? +this._endTime - +this._startTime : 0
163
+ }
164
+
165
+ /**
166
+ * All failed tests.
167
+ *
168
+ * @type {CodeceptJS.Test[]}
169
+ * readonly
170
+ */
171
+ get failedTests() {
172
+ return this._tests.filter(test => test.state === 'failed')
173
+ }
174
+
175
+ /**
176
+ * All passed tests.
177
+ *
178
+ * @type {CodeceptJS.Test[]}
179
+ * @readonly
180
+ */
181
+ get passedTests() {
182
+ return this._tests.filter(test => test.state === 'passed')
183
+ }
184
+
185
+ /**
186
+ * All skipped tests.
187
+ *
188
+ * @type {CodeceptJS.Test[]}
189
+ * @readonly
190
+ */
191
+ get skippedTests() {
192
+ return this._tests.filter(test => test.state === 'skipped' || test.state === 'pending')
193
+ }
194
+
195
+ /**
196
+ * @returns {object} The JSON representation of this result.
197
+ */
198
+ simplify() {
199
+ return {
200
+ hasFailed: this.hasFailed,
201
+ stats: this.stats,
202
+ duration: this.duration,
203
+ tests: this._tests.map(test => serializeTest(test)),
204
+ failures: this._failures,
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Saves this result to a JSON file.
210
+ *
211
+ * @param {string} [fileName] Path to the JSON file, relative to `output_dir`. Defaults to "result.json".
212
+ */
213
+ save(fileName) {
214
+ if (!fileName) fileName = 'result.json'
215
+ fs.writeFileSync(path.join(global.output_dir, fileName), JSON.stringify(this.simplify(), null, 2))
216
+ }
217
+
218
+ /**
219
+ * Adds stats to this result.
220
+ *
221
+ * @param {Partial<Stats>} [newStats]
222
+ */
223
+ addStats(newStats = {}) {
224
+ this._stats.passes += newStats.passes || 0
225
+ this._stats.failures += newStats.failures || 0
226
+ this._stats.tests += newStats.tests || 0
227
+ this._stats.pending += newStats.pending || 0
228
+ this._stats.failedHooks += newStats.failedHooks || 0
229
+
230
+ // do not override start time
231
+ this._stats.start = this._stats.start || newStats.start
232
+
233
+ this._stats.end = newStats.end || this._stats.end
234
+ this._stats.duration = newStats.duration
235
+ }
236
+ }
237
+
238
+ export default Result
@@ -0,0 +1,207 @@
1
+ import output from './output.js'
2
+
3
+ /**
4
+ * Retry Coordinator - Central coordination for all retry mechanisms
5
+ *
6
+ * This module provides:
7
+ * 1. Priority-based retry coordination
8
+ * 2. Unified configuration validation
9
+ * 3. Consolidated retry reporting
10
+ * 4. Conflict detection and resolution
11
+ */
12
+
13
+ /**
14
+ * Priority levels for retry mechanisms (higher number = higher priority)
15
+ */
16
+ const RETRY_PRIORITIES = {
17
+ MANUAL_STEP: 100, // I.retry() or step.retry() - highest priority
18
+ STEP_PLUGIN: 50, // retryFailedStep plugin
19
+ SCENARIO_CONFIG: 30, // Global scenario retry config
20
+ FEATURE_CONFIG: 20, // Global feature retry config
21
+ HOOK_CONFIG: 10, // Hook retry config - lowest priority
22
+ }
23
+
24
+ /**
25
+ * Retry mechanism types
26
+ */
27
+ const RETRY_TYPES = {
28
+ MANUAL_STEP: 'manual-step',
29
+ STEP_PLUGIN: 'step-plugin',
30
+ SCENARIO: 'scenario',
31
+ FEATURE: 'feature',
32
+ HOOK: 'hook',
33
+ }
34
+
35
+ /**
36
+ * Global retry coordination state
37
+ */
38
+ let retryState = {
39
+ activeTest: null,
40
+ activeSuite: null,
41
+ retryHistory: [],
42
+ conflicts: [],
43
+ }
44
+
45
+ /**
46
+ * Registers a retry mechanism for coordination
47
+ * @param {string} type - Type of retry mechanism
48
+ * @param {Object} config - Retry configuration
49
+ * @param {Object} target - Target object (test, suite, etc.)
50
+ * @param {number} priority - Priority level
51
+ */
52
+ function registerRetry(type, config, target, priority = 0) {
53
+ const retryInfo = {
54
+ type,
55
+ config,
56
+ target,
57
+ priority,
58
+ timestamp: Date.now(),
59
+ }
60
+
61
+ // Detect conflicts
62
+ const existingRetries = retryState.retryHistory.filter(r => r.target === target && r.type !== type && r.priority !== priority)
63
+
64
+ if (existingRetries.length > 0) {
65
+ const conflict = {
66
+ newRetry: retryInfo,
67
+ existingRetries: existingRetries,
68
+ resolved: false,
69
+ }
70
+ retryState.conflicts.push(conflict)
71
+ handleRetryConflict(conflict)
72
+ }
73
+
74
+ retryState.retryHistory.push(retryInfo)
75
+
76
+ output.log(`[Retry Coordinator] Registered ${type} retry (priority: ${priority})`)
77
+ }
78
+
79
+ /**
80
+ * Handles conflicts between retry mechanisms
81
+ * @param {Object} conflict - Conflict information
82
+ */
83
+ function handleRetryConflict(conflict) {
84
+ const { newRetry, existingRetries } = conflict
85
+
86
+ // Find highest priority retry
87
+ const allRetries = [newRetry, ...existingRetries]
88
+ const highestPriority = Math.max(...allRetries.map(r => r.priority))
89
+ const winningRetry = allRetries.find(r => r.priority === highestPriority)
90
+
91
+ // Log the conflict resolution
92
+ output.log(`[Retry Coordinator] Conflict detected:`)
93
+ allRetries.forEach(retry => {
94
+ const status = retry === winningRetry ? 'ACTIVE' : 'DEFERRED'
95
+ output.log(` - ${retry.type} (priority: ${retry.priority}) [${status}]`)
96
+ })
97
+
98
+ conflict.resolved = true
99
+ conflict.winner = winningRetry
100
+ }
101
+
102
+ /**
103
+ * Gets the effective retry configuration for a target
104
+ * @param {Object} target - Target object (test, suite, etc.)
105
+ * @returns {Object} Effective retry configuration
106
+ */
107
+ function getEffectiveRetryConfig(target) {
108
+ const targetRetries = retryState.retryHistory.filter(r => r.target === target)
109
+
110
+ if (targetRetries.length === 0) {
111
+ return { type: 'none', retries: 0 }
112
+ }
113
+
114
+ // Find highest priority retry
115
+ const highestPriority = Math.max(...targetRetries.map(r => r.priority))
116
+ const effectiveRetry = targetRetries.find(r => r.priority === highestPriority)
117
+
118
+ return {
119
+ type: effectiveRetry.type,
120
+ retries: effectiveRetry.config.retries || effectiveRetry.config,
121
+ config: effectiveRetry.config,
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Generates a retry summary report
127
+ * @returns {Object} Retry summary
128
+ */
129
+ function generateRetrySummary() {
130
+ const summary = {
131
+ totalRetryMechanisms: retryState.retryHistory.length,
132
+ conflicts: retryState.conflicts.length,
133
+ byType: {},
134
+ recommendations: [],
135
+ }
136
+
137
+ // Count by type
138
+ retryState.retryHistory.forEach(retry => {
139
+ summary.byType[retry.type] = (summary.byType[retry.type] || 0) + 1
140
+ })
141
+
142
+ // Generate recommendations
143
+ if (summary.conflicts > 0) {
144
+ summary.recommendations.push('Consider consolidating retry configurations to avoid conflicts')
145
+ }
146
+
147
+ if (summary.byType[RETRY_TYPES.STEP_PLUGIN] && summary.byType[RETRY_TYPES.SCENARIO]) {
148
+ summary.recommendations.push('Step-level and scenario-level retries are both active - consider using only one approach')
149
+ }
150
+
151
+ return summary
152
+ }
153
+
154
+ /**
155
+ * Resets the retry coordination state (useful for testing)
156
+ */
157
+ function reset() {
158
+ retryState = {
159
+ activeTest: null,
160
+ activeSuite: null,
161
+ retryHistory: [],
162
+ conflicts: [],
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Validates retry configuration for common issues
168
+ * @param {Object} config - Configuration object
169
+ * @returns {Array} Array of validation warnings
170
+ */
171
+ function validateConfig(config) {
172
+ const warnings = []
173
+
174
+ if (!config) return warnings
175
+
176
+ // Check for potential configuration conflicts
177
+ if (config.retry && config.plugins && config.plugins.retryFailedStep) {
178
+ const globalRetries = typeof config.retry === 'number' ? config.retry : config.retry.Scenario || config.retry.Feature
179
+ const stepRetries = config.plugins.retryFailedStep.retries || 3
180
+
181
+ if (globalRetries && stepRetries) {
182
+ warnings.push(`Both global retries (${globalRetries}) and step retries (${stepRetries}) are configured - total executions could be ${globalRetries * (stepRetries + 1)}`)
183
+ }
184
+ }
185
+
186
+ // Check for excessive retry counts
187
+ if (config.retry) {
188
+ const retryValues = typeof config.retry === 'number' ? [config.retry] : Object.values(config.retry)
189
+ const maxRetries = Math.max(...retryValues.filter(v => typeof v === 'number'))
190
+
191
+ if (maxRetries > 5) {
192
+ warnings.push(`High retry count detected (${maxRetries}) - consider investigating test stability instead`)
193
+ }
194
+ }
195
+
196
+ return warnings
197
+ }
198
+
199
+ export {
200
+ RETRY_PRIORITIES,
201
+ RETRY_TYPES,
202
+ registerRetry,
203
+ getEffectiveRetryConfig,
204
+ generateRetrySummary,
205
+ validateConfig,
206
+ reset,
207
+ }
package/lib/secret.js CHANGED
@@ -1,21 +1,20 @@
1
- /* eslint-disable max-classes-per-file */
2
- import { deepClone } from './utils.js';
1
+ import { deepClone } from './utils.js'
3
2
 
4
- const maskedString = '*****';
3
+ const maskedString = '*****'
5
4
 
6
5
  /** @param {string} secret */
7
- export default class Secret {
6
+ class Secret {
8
7
  constructor(secret) {
9
- this._secret = secret;
8
+ this._secret = secret
10
9
  }
11
10
 
12
11
  /** @returns {string} */
13
12
  toString() {
14
- return this._secret;
13
+ return this._secret
15
14
  }
16
15
 
17
16
  getMasked() {
18
- return maskedString;
17
+ return maskedString
19
18
  }
20
19
 
21
20
  /**
@@ -24,11 +23,11 @@ export default class Secret {
24
23
  */
25
24
  static secret(secret) {
26
25
  if (typeof secret === 'object') {
27
- const fields = Array.from(arguments);
28
- fields.shift();
29
- return secretObject(secret, fields);
26
+ const fields = Array.from(arguments)
27
+ fields.shift()
28
+ return secretObject(secret, fields)
30
29
  }
31
- return new Secret(secret);
30
+ return new Secret(secret)
32
31
  }
33
32
  }
34
33
 
@@ -37,14 +36,17 @@ function secretObject(obj, fieldsToHide = []) {
37
36
  get(obj, prop) {
38
37
  if (prop === 'toString') {
39
38
  return function () {
40
- const maskedObject = deepClone(obj);
41
- fieldsToHide.forEach(f => maskedObject[f] = maskedString);
42
- return JSON.stringify(maskedObject);
43
- };
39
+ const maskedObject = deepClone(obj)
40
+ fieldsToHide.forEach(f => (maskedObject[f] = maskedString))
41
+ return JSON.stringify(maskedObject)
42
+ }
44
43
  }
45
- return fieldsToHide.includes(prop) ? new Secret(obj[prop]) : obj[prop];
44
+ return fieldsToHide.includes(prop) ? new Secret(obj[prop]) : obj[prop]
46
45
  },
47
- };
46
+ }
48
47
 
49
- return new Proxy(obj, handler);
48
+ return new Proxy(obj, handler)
50
49
  }
50
+
51
+ export default Secret
52
+ export const secret = Secret.secret