codeceptjs 4.0.0-beta.2 → 4.0.0-beta.20

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 +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 +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 +641 -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 +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/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
@@ -1,21 +1,25 @@
1
- import debug from 'debug';
2
- import { CoverageReport } from 'monocart-coverage-reports';
3
- import container from '../container.js';
4
- import recorder from '../recorder.js';
5
- import * as event from '../event.js';
6
- import * as output from '../output.js';
7
- import { deepMerge } from '../utils.js';
1
+ import debugModule from 'debug'
2
+ import { CoverageReport } from 'monocart-coverage-reports'
3
+ import Container from '../container.js'
4
+
5
+ import recorder from '../recorder.js'
6
+
7
+ import event from '../event.js'
8
+
9
+ import output from '../output.js'
10
+
11
+ import { deepMerge } from '../utils.js'
8
12
 
9
13
  const defaultConfig = {
10
14
  name: 'CodeceptJS Coverage Report',
11
15
  outputDir: 'output/coverage',
12
- };
16
+ }
13
17
 
14
- const supportedHelpers = ['Puppeteer', 'Playwright'];
18
+ const supportedHelpers = ['Puppeteer', 'Playwright', 'WebDriver']
15
19
 
16
20
  const v8CoverageHelpers = {
17
21
  Playwright: {
18
- startCoverage: async (page) => {
22
+ startCoverage: async page => {
19
23
  await Promise.all([
20
24
  page.coverage.startJSCoverage({
21
25
  resetOnNavigation: false,
@@ -23,19 +27,16 @@ const v8CoverageHelpers = {
23
27
  page.coverage.startCSSCoverage({
24
28
  resetOnNavigation: false,
25
29
  }),
26
- ]);
30
+ ])
27
31
  },
28
32
  takeCoverage: async (page, coverageReport) => {
29
- const [jsCoverage, cssCoverage] = await Promise.all([
30
- page.coverage.stopJSCoverage(),
31
- page.coverage.stopCSSCoverage(),
32
- ]);
33
- const coverageList = [...jsCoverage, ...cssCoverage];
34
- await coverageReport.add(coverageList);
33
+ const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
34
+ const coverageList = [...jsCoverage, ...cssCoverage]
35
+ await coverageReport.add(coverageList)
35
36
  },
36
37
  },
37
38
  Puppeteer: {
38
- startCoverage: async (page) => {
39
+ startCoverage: async page => {
39
40
  await Promise.all([
40
41
  page.coverage.startJSCoverage({
41
42
  resetOnNavigation: false,
@@ -44,24 +45,51 @@ const v8CoverageHelpers = {
44
45
  page.coverage.startCSSCoverage({
45
46
  resetOnNavigation: false,
46
47
  }),
47
- ]);
48
+ ])
48
49
  },
49
50
  takeCoverage: async (page, coverageReport) => {
50
- const [jsCoverage, cssCoverage] = await Promise.all([
51
- page.coverage.stopJSCoverage(),
52
- page.coverage.stopCSSCoverage(),
53
- ]);
51
+ const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
54
52
  // to raw V8 script coverage
55
- const coverageList = [...jsCoverage.map((it) => {
56
- return {
57
- source: it.text,
58
- ...it.rawScriptCoverage,
59
- };
60
- }), ...cssCoverage];
61
- await coverageReport.add(coverageList);
53
+ const coverageList = [
54
+ ...jsCoverage.map(it => {
55
+ return {
56
+ source: it.text,
57
+ ...it.rawScriptCoverage,
58
+ }
59
+ }),
60
+ ...cssCoverage,
61
+ ]
62
+ await coverageReport.add(coverageList)
62
63
  },
63
64
  },
64
- };
65
+ WebDriver: {
66
+ startCoverage: async page => {
67
+ await Promise.all([
68
+ page.coverage.startJSCoverage({
69
+ resetOnNavigation: false,
70
+ includeRawScriptCoverage: true,
71
+ }),
72
+ page.coverage.startCSSCoverage({
73
+ resetOnNavigation: false,
74
+ }),
75
+ ])
76
+ },
77
+ takeCoverage: async (page, coverageReport) => {
78
+ const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
79
+ // to raw V8 script coverage
80
+ const coverageList = [
81
+ ...jsCoverage.map(it => {
82
+ return {
83
+ source: it.text,
84
+ ...it.rawScriptCoverage,
85
+ }
86
+ }),
87
+ ...cssCoverage,
88
+ ]
89
+ await coverageReport.add(coverageList)
90
+ },
91
+ },
92
+ }
65
93
 
66
94
  /**
67
95
  * Dumps code coverage from Playwright/Puppeteer after every test.
@@ -89,67 +117,92 @@ const v8CoverageHelpers = {
89
117
  * * `sourcePath`: option to resolve a custom path.
90
118
  *
91
119
  */
92
-
93
120
  export default function (config) {
94
- config = deepMerge(defaultConfig, config);
95
- if (config.debug) config.logging = 'debug';
96
- const helpers = container.helpers();
97
- console.log(helpers);
98
- let coverageRunning = false;
99
-
100
- const v8Names = Object.keys(v8CoverageHelpers);
101
- const helperName = Object.keys(helpers).find((it) => v8Names.includes(it));
121
+ config = deepMerge(defaultConfig, config)
122
+
123
+ if (config.debug) config.logging = 'debug'
124
+
125
+ const helpers = Container.helpers()
126
+ let coverageRunning = false
127
+
128
+ const v8Names = Object.keys(v8CoverageHelpers)
129
+ const helperName = Object.keys(helpers).find(it => v8Names.includes(it))
102
130
  if (!helperName) {
103
- console.error(`Coverage is only supported in ${supportedHelpers.join(' or ')}`);
131
+ console.error(`Coverage is only supported in ${supportedHelpers.join(' or ')}`)
104
132
  // no helpers for screenshot
105
- return;
133
+ return
106
134
  }
107
135
 
108
- config.name = `${config.name} - in ${helperName}`;
109
- const logger = debug(`codeceptjs:plugin:${helperName.toLowerCase()}Coverage`);
136
+ config.name = `${config.name} - in ${helperName}`
137
+ const debug = debugModule(`codeceptjs:plugin:${helperName.toLowerCase()}Coverage`)
138
+
139
+ const helper = helpers[helperName]
110
140
 
111
- const helper = helpers[helperName];
112
- const v8Helper = v8CoverageHelpers[helperName];
141
+ const v8Helper = v8CoverageHelpers[helperName]
113
142
 
114
143
  const coverageOptions = {
115
144
  ...config,
116
- };
117
- const coverageReport = new CoverageReport(coverageOptions);
118
- coverageReport.cleanCache();
145
+ }
146
+
147
+ if (helperName === 'WebDriver') coverageOptions.coverageProvider = 'v8'
148
+
149
+ const coverageReport = new CoverageReport(coverageOptions)
150
+ coverageReport.cleanCache()
119
151
 
120
- event.dispatcher.on(event.all.after, async () => {
121
- output.print(`writing ${coverageOptions.outputDir}`);
122
- await coverageReport.generate();
123
- });
152
+ event.dispatcher.on(event.all.after, () => {
153
+ // Add coverage generation to recorder to ensure it completes before process exit
154
+ recorder.add(
155
+ 'generate coverage report',
156
+ async () => {
157
+ try {
158
+ output.print(`writing ${coverageOptions.outputDir}`)
159
+ await coverageReport.generate()
160
+ } catch (error) {
161
+ output.print(`Failed to generate coverage report: ${error.message}`)
162
+ // Don't throw - coverage failure shouldn't fail tests
163
+ }
164
+ },
165
+ true,
166
+ false,
167
+ )
168
+ })
124
169
 
125
170
  // we're going to try to "start" coverage before each step because this is
126
171
  // when the browser is already up and is ready to start coverage.
127
172
  event.dispatcher.on(event.step.before, () => {
128
- recorder.add('start coverage', async () => {
129
- if (coverageRunning) {
130
- return;
131
- }
132
- if (!helper.page || !helper.page.coverage) {
133
- return;
134
- }
135
- coverageRunning = true;
136
- logger('--> starting coverage <--');
137
- await v8Helper.startCoverage(helper.page);
138
- }, true);
139
- });
173
+ recorder.add(
174
+ 'start coverage',
175
+ async () => {
176
+ if (coverageRunning) {
177
+ return
178
+ }
179
+ if (!helper.page || !helper.page.coverage) {
180
+ return
181
+ }
182
+ coverageRunning = true
183
+ debug('--> starting coverage <--')
184
+ await v8Helper.startCoverage(helper.page)
185
+ },
186
+ true,
187
+ )
188
+ })
140
189
 
141
190
  // Save coverage data after every test run
142
- event.dispatcher.on(event.test.after, (test) => {
143
- recorder.add('take coverage', async () => {
144
- if (!coverageRunning) {
145
- return;
146
- }
147
- if (!helper.page || !helper.page.coverage) {
148
- return;
149
- }
150
- coverageRunning = false;
151
- logger('--> stopping coverage <--');
152
- await v8Helper.takeCoverage(helper.page, coverageReport);
153
- }, true);
154
- });
191
+ event.dispatcher.on(event.test.after, test => {
192
+ recorder.add(
193
+ 'take coverage',
194
+ async () => {
195
+ if (!coverageRunning) {
196
+ return
197
+ }
198
+ if (!helper.page || !helper.page.coverage) {
199
+ return
200
+ }
201
+ coverageRunning = false
202
+ debug('--> stopping coverage <--')
203
+ await v8Helper.takeCoverage(helper.page, coverageReport)
204
+ },
205
+ true,
206
+ )
207
+ })
155
208
  }
@@ -1,12 +1,12 @@
1
- import Locator from '../locator.js';
2
- import { xpathLocator } from '../utils.js';
1
+ import Locator from '../locator.js'
2
+ import { xpathLocator } from '../utils.js'
3
3
 
4
4
  const defaultConfig = {
5
5
  prefix: '$',
6
6
  attribute: 'data-test-id',
7
7
  strategy: 'xpath',
8
8
  showActual: false,
9
- };
9
+ }
10
10
 
11
11
  /**
12
12
  * Creates a [custom locator](https://codecept.io/locators#custom-locators) by using special attributes in HTML.
@@ -111,34 +111,35 @@ const defaultConfig = {
111
111
  * I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
112
112
  * ```
113
113
  */
114
- export default (config) => {
115
- config = { ...defaultConfig, ...config };
114
+ export default function (config) {
115
+ config = { ...defaultConfig, ...config }
116
116
 
117
117
  Locator.addFilter((value, locatorObj) => {
118
- if (typeof value !== 'string') return;
119
- if (!value.startsWith(config.prefix)) return;
118
+ if (typeof value !== 'string') return
119
+ if (!value.startsWith(config.prefix)) return
120
120
 
121
- if (!['String', 'Array'].includes(config.attribute.constructor.name)) return;
121
+ if (!['String', 'Array'].includes(config.attribute.constructor.name)) return
122
122
 
123
- const val = value.substr(config.prefix.length);
123
+ const val = value.substr(config.prefix.length)
124
124
 
125
125
  if (config.strategy.toLowerCase() === 'xpath') {
126
- locatorObj.value = `.//*[${
127
- [].concat(config.attribute)
128
- .map((attr) => `@${attr}=${xpathLocator.literal(val)}`)
129
- .join(' or ')}]`;
130
- locatorObj.type = 'xpath';
126
+ locatorObj.value = `.//*[${[]
127
+ .concat(config.attribute)
128
+ .map(attr => `@${attr}=${xpathLocator.literal(val)}`)
129
+ .join(' or ')}]`
130
+ locatorObj.type = 'xpath'
131
131
  }
132
132
 
133
133
  if (config.strategy.toLowerCase() === 'css') {
134
- locatorObj.value = [].concat(config.attribute)
135
- .map((attr) => `[${attr}=${val}]`)
136
- .join(',');
137
- locatorObj.type = 'css';
134
+ locatorObj.value = []
135
+ .concat(config.attribute)
136
+ .map(attr => `[${attr}=${val}]`)
137
+ .join(',')
138
+ locatorObj.type = 'css'
138
139
  }
139
140
 
140
141
  if (config.showActual) {
141
- locatorObj.output = locatorObj.value;
142
+ locatorObj.output = locatorObj.value
142
143
  }
143
- });
144
- };
144
+ })
145
+ }
@@ -0,0 +1,53 @@
1
+ import event from '../event.js'
2
+
3
+
4
+ /**
5
+ * Sample custom reporter for CodeceptJS.
6
+ */
7
+ export default function (config) {
8
+ event.dispatcher.on(event.hook.finished, hook => {
9
+ if (config.onHookFinished) {
10
+ config.onHookFinished(hook)
11
+ }
12
+ })
13
+
14
+ event.dispatcher.on(event.test.before, test => {
15
+ if (config.onTestBefore) {
16
+ config.onTestBefore(test)
17
+ }
18
+ })
19
+
20
+ event.dispatcher.on(event.test.failed, (test, err) => {
21
+ if (config.onTestFailed) {
22
+ config.onTestFailed(test, err)
23
+ }
24
+ })
25
+
26
+ event.dispatcher.on(event.test.passed, test => {
27
+ if (config.onTestPassed) {
28
+ config.onTestPassed(test)
29
+ }
30
+ })
31
+
32
+ event.dispatcher.on(event.test.skipped, test => {
33
+ if (config.onTestSkipped) {
34
+ config.onTestSkipped(test)
35
+ }
36
+ })
37
+
38
+ event.dispatcher.on(event.test.finished, test => {
39
+ if (config.onTestFinished) {
40
+ config.onTestFinished(test)
41
+ }
42
+ })
43
+
44
+ event.dispatcher.on(event.all.result, result => {
45
+ if (config.onResult) {
46
+ config.onResult(result)
47
+ }
48
+
49
+ if (config.save) {
50
+ result.save()
51
+ }
52
+ })
53
+ }
@@ -0,0 +1,99 @@
1
+ import event from '../event.js'
2
+ import recorder from '../recorder.js'
3
+ import store from '../store.js'
4
+ import output from '../output.js'
5
+ import { RETRY_PRIORITIES } from '../retryCoordinator.js'
6
+
7
+ const defaultConfig = {
8
+ retries: 3,
9
+ defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
10
+ factor: 1.5,
11
+ ignoredSteps: [],
12
+ }
13
+
14
+ /**
15
+ * Enhanced retryFailedStep plugin that coordinates with other retry mechanisms
16
+ *
17
+ * This plugin provides step-level retries and coordinates with global retry settings
18
+ * to avoid conflicts and provide predictable behavior.
19
+ */
20
+ export default config => {
21
+ config = Object.assign({}, defaultConfig, config)
22
+ config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
23
+ const customWhen = config.when
24
+
25
+ let enableRetry = false
26
+
27
+ const when = err => {
28
+ if (!enableRetry) return false
29
+ if (store.debugMode) return false
30
+ if (!store.autoRetries) return false
31
+ if (customWhen) return customWhen(err)
32
+ return true
33
+ }
34
+ config.when = when
35
+
36
+ event.dispatcher.on(event.step.started, step => {
37
+ // if a step is ignored - return
38
+ for (const ignored of config.ignoredSteps) {
39
+ if (step.name === ignored) return
40
+ if (ignored instanceof RegExp) {
41
+ if (step.name.match(ignored)) return
42
+ } else if (ignored.indexOf('*') && step.name.startsWith(ignored.slice(0, -1))) return
43
+ }
44
+ enableRetry = true // enable retry for a step
45
+ })
46
+
47
+ event.dispatcher.on(event.step.finished, () => {
48
+ enableRetry = false
49
+ })
50
+
51
+ event.dispatcher.on(event.test.before, test => {
52
+ // pass disableRetryFailedStep is a preferred way to disable retries
53
+ // test.disableRetryFailedStep is used for backward compatibility
54
+ if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
55
+ store.autoRetries = false
56
+ output.log(`[Step Retry] Disabled for test: ${test.title}`)
57
+ return // disable retry when a test is not active
58
+ }
59
+
60
+ // Check if step retries should be disabled due to higher priority scenario retries
61
+ const scenarioRetries = test.retries()
62
+ const stepRetryPriority = RETRY_PRIORITIES.STEP_PLUGIN
63
+ const scenarioPriority = test.opts.retryPriority || 0
64
+
65
+ if (scenarioRetries > 0 && config.deferToScenarioRetries !== false) {
66
+ // Scenario retries are configured with higher or equal priority
67
+ // Option 1: Disable step retries (conservative approach)
68
+ store.autoRetries = false
69
+ output.log(`[Step Retry] Deferred to scenario retries (${scenarioRetries} retries)`)
70
+ return
71
+
72
+ // Option 2: Reduce step retries to avoid excessive total retries
73
+ // const reducedStepRetries = Math.max(1, Math.floor(config.retries / scenarioRetries))
74
+ // config.retries = reducedStepRetries
75
+ // output.log(`[Step Retry] Reduced to ${reducedStepRetries} retries due to scenario retries (${scenarioRetries})`)
76
+ }
77
+
78
+ // this option is used to set the retries inside _before() block of helpers
79
+ store.autoRetries = true
80
+ test.opts.conditionalRetries = config.retries
81
+ test.opts.stepRetryPriority = stepRetryPriority
82
+
83
+ recorder.retry(config)
84
+
85
+ output.log(`[Step Retry] Enabled with ${config.retries} retries for test: ${test.title}`)
86
+ })
87
+
88
+ // Add coordination info for debugging
89
+ event.dispatcher.on(event.test.finished, test => {
90
+ if (test.state === 'passed' && test.opts.conditionalRetries && store.autoRetries) {
91
+ const stepRetries = test.opts.conditionalRetries || 0
92
+ const scenarioRetries = test.retries() || 0
93
+
94
+ if (stepRetries > 0 && scenarioRetries > 0) {
95
+ output.log(`[Retry Coordination] Test used both step retries (${stepRetries}) and scenario retries (${scenarioRetries})`)
96
+ }
97
+ }
98
+ })
99
+ }