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
package/lib/history.js CHANGED
@@ -1,7 +1,7 @@
1
- import colors from 'chalk';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import * as output from './output.js';
1
+ import colors from 'chalk'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import output from './output.js'
5
5
 
6
6
  /**
7
7
  * REPL history records REPL commands and stores them in
@@ -10,42 +10,46 @@ import * as output from './output.js';
10
10
  class ReplHistory {
11
11
  constructor() {
12
12
  if (global.output_dir) {
13
- this.historyFile = path.join(global.output_dir, 'cli-history');
13
+ this.historyFile = path.join(global.output_dir, 'cli-history')
14
14
  }
15
- this.commands = [];
15
+ this.commands = []
16
16
  }
17
17
 
18
18
  push(cmd) {
19
- this.commands.push(cmd);
19
+ this.commands.push(cmd)
20
20
  }
21
21
 
22
22
  pop() {
23
- this.commands.pop();
23
+ this.commands.pop()
24
24
  }
25
25
 
26
26
  load() {
27
- if (!this.historyFile) return;
27
+ if (!this.historyFile) return
28
28
  if (!fs.existsSync(this.historyFile)) {
29
- return;
29
+ return
30
30
  }
31
31
 
32
- const history = fs.readFileSync(this.historyFile, 'utf-8');
33
- return history.split('\n').reverse().filter(line => line.startsWith('I.')).map(line => line.slice(2));
32
+ const history = fs.readFileSync(this.historyFile, 'utf-8')
33
+ return history
34
+ .split('\n')
35
+ .reverse()
36
+ .filter(line => line.startsWith('I.'))
37
+ .map(line => line.slice(2))
34
38
  }
35
39
 
36
40
  save() {
37
- if (!this.historyFile) return;
41
+ if (!this.historyFile) return
38
42
  if (this.commands.length === 0) {
39
- return;
43
+ return
40
44
  }
41
45
 
42
- const commandSnippet = `\n\n<<< Recorded commands on ${new Date()}\n${this.commands.join('\n')}`;
43
- fs.appendFileSync(this.historyFile, commandSnippet);
46
+ const commandSnippet = `\n\n<<< Recorded commands on ${new Date()}\n${this.commands.join('\n')}`
47
+ fs.appendFileSync(this.historyFile, commandSnippet)
44
48
 
45
- output.print(colors.yellow(` Commands have been saved to ${this.historyFile}`));
49
+ output.print(colors.yellow(` Commands have been saved to ${this.historyFile}`))
46
50
 
47
- this.commands = [];
51
+ this.commands = []
48
52
  }
49
53
  }
50
54
 
51
- export default new ReplHistory();
55
+ export default new ReplHistory()
package/lib/hooks.js CHANGED
@@ -1,17 +1,17 @@
1
- import { isFunction, isAsyncFunction } from './utils.js';
2
- import * as output from './output.js';
1
+ import { isFunction, isAsyncFunction } from './utils.js'
2
+ import output from './output.js'
3
3
 
4
4
  export default async function (hook, stage) {
5
- if (!hook) return;
5
+ if (!hook) return
6
6
  if (!isFunction(hook)) {
7
- throw new Error('CodeceptJS 3 allows bootstrap/teardown hooks only as async functions. More info: https://bit.ly/codecept3Up');
7
+ throw new Error('CodeceptJS 3 allows bootstrap/teardown hooks only as async functions. More info: https://bit.ly/codecept3Up')
8
8
  }
9
9
 
10
- if (stage) output.output.log(`started ${stage} hook`);
10
+ if (stage) output.log(`started ${stage} hook`)
11
11
  if (isAsyncFunction(hook)) {
12
- await hook();
12
+ await hook()
13
13
  } else {
14
- hook();
14
+ hook()
15
15
  }
16
- if (stage) output.output.log(`finished ${stage} hook`);
16
+ if (stage) output.log(`finished ${stage} hook`)
17
17
  }
package/lib/html.js CHANGED
@@ -1,7 +1,7 @@
1
- import { parse, serialize } from 'parse5';
2
- import { minify } from 'html-minifier-terser';
1
+ import { parse, serialize } from 'parse5'
2
+ import { minify } from 'html-minifier-terser'
3
3
 
4
- export async function minifyHtml(html) {
4
+ async function minifyHtml(html) {
5
5
  return minify(html, {
6
6
  collapseWhitespace: true,
7
7
  removeComments: true,
@@ -11,7 +11,7 @@ export async function minifyHtml(html) {
11
11
  removeStyleLinkTypeAttributes: true,
12
12
  collapseBooleanAttributes: true,
13
13
  useShortDoctype: true,
14
- });
14
+ })
15
15
  }
16
16
 
17
17
  const defaultHtmlOpts = {
@@ -19,240 +19,230 @@ const defaultHtmlOpts = {
19
19
  textElements: ['label', 'h1', 'h2'],
20
20
  allowedAttrs: ['id', 'for', 'class', 'name', 'type', 'value', 'tabindex', 'aria-labelledby', 'aria-label', 'label', 'placeholder', 'title', 'alt', 'src', 'role'],
21
21
  allowedRoles: ['button', 'checkbox', 'search', 'textbox', 'tab'],
22
- };
22
+ }
23
23
 
24
- export function removeNonInteractiveElements(html, opts = {}) {
25
- opts = { ...defaultHtmlOpts, ...opts };
26
- const {
27
- interactiveElements,
28
- textElements,
29
- allowedAttrs,
30
- allowedRoles,
31
- } = opts;
24
+ function removeNonInteractiveElements(html, opts = {}) {
25
+ opts = { ...defaultHtmlOpts, ...opts }
26
+ const { interactiveElements, textElements, allowedAttrs, allowedRoles } = opts
32
27
 
33
28
  // Parse the HTML into a document tree
34
- const document = parse(html);
29
+ const document = parse(html)
35
30
 
36
- const trashHtmlClasses = /^(text-|color-|flex-|float-|v-|ember-|d-|border-)/;
31
+ const trashHtmlClasses = /^(text-|color-|flex-|float-|v-|ember-|d-|border-)/
37
32
  // Array to store interactive elements
38
- const removeElements = ['path', 'script'];
33
+ const removeElements = ['path', 'script']
39
34
 
40
35
  function isFilteredOut(node) {
41
- if (removeElements.includes(node.nodeName)) return true;
36
+ if (removeElements.includes(node.nodeName)) return true
42
37
  if (node.attrs) {
43
- if (node.attrs.find(attr => attr.name === 'role' && attr.value === 'tooltip')) return true;
38
+ if (node.attrs.find(attr => attr.name === 'role' && attr.value === 'tooltip')) return true
44
39
  }
45
- return false;
40
+ return false
46
41
  }
47
42
 
48
43
  // Function to check if an element is interactive
49
44
  function isInteractive(element) {
50
- if (element.nodeName === 'input' && element.attrs.find(attr => attr.name === 'type' && attr.value === 'hidden')) return false;
51
- if (interactiveElements.includes(element.nodeName)) return true;
45
+ if (element.nodeName === 'input' && element.attrs.find(attr => attr.name === 'type' && attr.value === 'hidden')) return false
46
+ if (interactiveElements.includes(element.nodeName)) return true
52
47
  if (element.attrs) {
53
- if (element.attrs.find(attr => attr.name === 'contenteditable')) return true;
54
- if (element.attrs.find(attr => attr.name === 'tabindex')) return true;
55
- const role = element.attrs.find(attr => attr.name === 'role');
56
- if (role && allowedRoles.includes(role.value)) return true;
48
+ if (element.attrs.find(attr => attr.name === 'contenteditable')) return true
49
+ if (element.attrs.find(attr => attr.name === 'tabindex')) return true
50
+ const role = element.attrs.find(attr => attr.name === 'role')
51
+ if (role && allowedRoles.includes(role.value)) return true
57
52
  }
58
- return false;
53
+ return false
59
54
  }
60
55
 
61
56
  function hasMeaningfulText(node) {
62
- if (textElements.includes(node.nodeName)) return true;
63
- return false;
57
+ if (textElements.includes(node.nodeName)) return true
58
+ return false
64
59
  }
65
60
 
66
61
  function hasInteractiveDescendant(node) {
67
- if (!node.childNodes) return false;
68
- let result = false;
62
+ if (!node.childNodes) return false
63
+ let result = false
69
64
 
70
65
  for (const childNode of node.childNodes) {
71
- if (isInteractive(childNode) || hasMeaningfulText(childNode)) return true;
72
- result = result || hasInteractiveDescendant(childNode);
66
+ if (isInteractive(childNode) || hasMeaningfulText(childNode)) return true
67
+ result = result || hasInteractiveDescendant(childNode)
73
68
  }
74
69
 
75
- return result;
70
+ return result
76
71
  }
77
72
 
78
73
  // Function to remove non-interactive elements recursively
79
74
  function removeNonInteractive(node) {
80
75
  if (node.nodeName !== '#document') {
81
- const parent = node.parentNode;
82
- const index = parent.childNodes.indexOf(node);
76
+ const parent = node.parentNode
77
+ const index = parent.childNodes.indexOf(node)
83
78
 
84
79
  if (isFilteredOut(node)) {
85
- parent.childNodes.splice(index, 1);
86
- return true;
80
+ parent.childNodes.splice(index, 1)
81
+ return true
87
82
  }
88
83
 
89
84
  // keep texts for interactive elements
90
85
  if ((isInteractive(parent) || hasMeaningfulText(parent)) && node.nodeName === '#text') {
91
- node.value = node.value.trim().slice(0, 200);
92
- if (!node.value) return false;
93
- return true;
86
+ node.value = node.value.trim().slice(0, 200)
87
+ if (!node.value) return false
88
+ return true
94
89
  }
95
90
 
96
91
  if (
97
92
  // if parent is interactive, we may need child element to match
98
- !isInteractive(parent)
99
- && !isInteractive(node)
100
- && !hasInteractiveDescendant(node)
101
- && !hasMeaningfulText(node)) {
102
- parent.childNodes.splice(index, 1);
103
- return true;
93
+ !isInteractive(parent) &&
94
+ !isInteractive(node) &&
95
+ !hasInteractiveDescendant(node) &&
96
+ !hasMeaningfulText(node)
97
+ ) {
98
+ parent.childNodes.splice(index, 1)
99
+ return true
104
100
  }
105
101
  }
106
102
 
107
103
  if (node.attrs) {
108
104
  // Filter and keep allowed attributes, accessibility attributes
109
105
  node.attrs = node.attrs.filter(attr => {
110
- const { name, value } = attr;
106
+ const { name, value } = attr
111
107
  if (name === 'class') {
112
108
  // Remove classes containing digits
113
- attr.value = value.split(' ')
109
+ attr.value = value
110
+ .split(' ')
114
111
  // remove classes containing digits/
115
112
  .filter(className => !/\d/.test(className))
116
113
  // remove popular trash classes
117
114
  .filter(className => !className.match(trashHtmlClasses))
118
115
  // remove classes with : and __ in them
119
116
  .filter(className => !className.match(/(:|__)/))
120
- .join(' ');
117
+ .join(' ')
121
118
  }
122
119
 
123
- return allowedAttrs.includes(name);
124
- });
120
+ return allowedAttrs.includes(name)
121
+ })
125
122
  }
126
123
 
127
124
  if (node.childNodes) {
128
125
  for (let i = node.childNodes.length - 1; i >= 0; i--) {
129
- const childNode = node.childNodes[i];
130
- removeNonInteractive(childNode);
126
+ const childNode = node.childNodes[i]
127
+ removeNonInteractive(childNode)
131
128
  }
132
129
  }
133
- return false;
130
+ return false
134
131
  }
135
132
 
136
133
  // Remove non-interactive elements starting from the root element
137
- removeNonInteractive(document);
134
+ removeNonInteractive(document)
138
135
 
139
136
  // Serialize the modified document tree back to HTML
140
- const serializedHTML = serialize(document);
137
+ const serializedHTML = serialize(document)
141
138
 
142
- return serializedHTML;
139
+ return serializedHTML
143
140
  }
144
141
 
145
- export function scanForErrorMessages(html, errorClasses = []) {
142
+ function scanForErrorMessages(html, errorClasses = []) {
146
143
  // Parse the HTML into a document tree
147
- const document = parse(html);
144
+ const document = parse(html)
148
145
 
149
146
  // Array to store error messages
150
- const errorMessages = [];
147
+ const errorMessages = []
151
148
 
152
149
  // Function to recursively scan for error classes and messages
153
150
  function scanErrors(node) {
154
151
  if (node.attrs) {
155
- const classAttr = node.attrs.find(attr => attr.name === 'class');
152
+ const classAttr = node.attrs.find(attr => attr.name === 'class')
156
153
  if (classAttr && classAttr.value) {
157
- const classNameChunks = classAttr.value.split(' ');
158
- const errorClassFound = errorClasses.some(errorClass => classNameChunks.includes(errorClass));
154
+ const classNameChunks = classAttr.value.split(' ')
155
+ const errorClassFound = errorClasses.some(errorClass => classNameChunks.includes(errorClass))
159
156
  if (errorClassFound && node.childNodes) {
160
- const errorMessage = sanitizeTextContent(node);
161
- errorMessages.push(errorMessage);
157
+ const errorMessage = sanitizeTextContent(node)
158
+ errorMessages.push(errorMessage)
162
159
  }
163
160
  }
164
161
  }
165
162
 
166
163
  if (node.childNodes) {
167
164
  for (const childNode of node.childNodes) {
168
- scanErrors(childNode);
165
+ scanErrors(childNode)
169
166
  }
170
167
  }
171
168
  }
172
169
 
173
170
  // Start scanning for error classes and messages from the root element
174
- scanErrors(document);
171
+ scanErrors(document)
175
172
 
176
- return errorMessages;
173
+ return errorMessages
177
174
  }
178
175
 
179
176
  function sanitizeTextContent(node) {
180
177
  if (node.nodeName === '#text') {
181
- return node.value.trim();
178
+ return node.value.trim()
182
179
  }
183
180
 
184
- let sanitizedText = '';
181
+ let sanitizedText = ''
185
182
 
186
183
  if (node.childNodes) {
187
184
  for (const childNode of node.childNodes) {
188
- sanitizedText += sanitizeTextContent(childNode);
185
+ sanitizedText += sanitizeTextContent(childNode)
189
186
  }
190
187
  }
191
188
 
192
- return sanitizedText;
189
+ return sanitizedText
193
190
  }
194
191
 
195
192
  function buildPath(node, path = '') {
196
- const tag = node.nodeName;
197
- let attributes = '';
193
+ const tag = node.nodeName
194
+ let attributes = ''
198
195
 
199
196
  if (node.attrs) {
200
- attributes = node.attrs
201
- .map(attr => `${attr.name}="${attr.value}"`)
202
- .join(' ');
197
+ attributes = node.attrs.map(attr => `${attr.name}="${attr.value}"`).join(' ')
203
198
  }
204
199
 
205
200
  if (!tag.startsWith('#') && tag !== 'body' && tag !== 'html') {
206
- path += `<${node.nodeName}${node.attrs ? ` ${attributes}` : ''}>`;
201
+ path += `<${node.nodeName}${node.attrs ? ` ${attributes}` : ''}>`
207
202
  }
208
203
 
209
- if (!node.childNodes) return path;
204
+ if (!node.childNodes) return path
210
205
 
211
- const children = node.childNodes.filter(child => !child.nodeName.startsWith('#'));
206
+ const children = node.childNodes.filter(child => !child.nodeName.startsWith('#'))
212
207
 
213
208
  if (children.length) {
214
- return buildPath(children[children.length - 1], path);
209
+ return buildPath(children[children.length - 1], path)
215
210
  }
216
- return path;
211
+ return path
217
212
  }
218
213
 
219
- export function splitByChunks(text, chunkSize) {
220
- chunkSize -= 20;
221
- const chunks = [];
214
+ function splitByChunks(text, chunkSize) {
215
+ chunkSize -= 20
216
+ const chunks = []
222
217
  for (let i = 0; i < text.length; i += chunkSize) {
223
- chunks.push(text.slice(i, i + chunkSize));
218
+ chunks.push(text.slice(i, i + chunkSize))
224
219
  }
225
220
 
226
- const regex = /<\s*\w+(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^>\s]+)))*\s*$/;
221
+ const regex = /<\s*\w+(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^>\s]+)))*\s*$/
227
222
 
228
223
  // append tag to chunk if it was split out
229
224
  for (const index in chunks) {
230
- const nextIndex = parseInt(index, 10) + 1;
231
- if (!chunks[nextIndex]) break;
225
+ const nextIndex = parseInt(index, 10) + 1
226
+ if (!chunks[nextIndex]) break
232
227
 
233
- const currentChunk = chunks[index];
234
- const nextChunk = chunks[nextIndex];
228
+ const currentChunk = chunks[index]
229
+ const nextChunk = chunks[nextIndex]
235
230
 
236
- const lastTag = currentChunk.match(regex);
231
+ const lastTag = currentChunk.match(regex)
237
232
  if (lastTag) {
238
- chunks[nextIndex] = lastTag[0] + nextChunk;
233
+ chunks[nextIndex] = lastTag[0] + nextChunk
239
234
  }
240
235
 
241
- const path = buildPath(parse(currentChunk));
236
+ const path = buildPath(parse(currentChunk))
242
237
  if (path) {
243
- chunks[nextIndex] = path + chunks[nextIndex];
238
+ chunks[nextIndex] = path + chunks[nextIndex]
244
239
  }
245
240
 
246
- if (chunks[nextIndex].includes('<html')) continue;
247
- chunks[nextIndex] = `<html><body>${chunks[nextIndex]}</body></html>`;
241
+ if (chunks[nextIndex].includes('<html')) continue
242
+ chunks[nextIndex] = `<html><body>${chunks[nextIndex]}</body></html>`
248
243
  }
249
244
 
250
- return chunks.map(chunk => chunk.trim());
245
+ return chunks.map(chunk => chunk.trim())
251
246
  }
252
247
 
253
- export default {
254
- scanForErrorMessages,
255
- removeNonInteractiveElements,
256
- splitByChunks,
257
- minifyHtml,
258
- };
248
+ export { scanForErrorMessages, removeNonInteractiveElements, splitByChunks, minifyHtml }
package/lib/index.js CHANGED
@@ -1,20 +1,3 @@
1
- import * as _codecept from './codecept.js';
2
- import * as workers from './workers.js';
3
- import * as dataTableArgument from './data/dataTableArgument.js';
4
- import * as dataTable from './data/table.js';
5
- import * as within from './within.js';
6
- import * as pause from './pause.js';
7
- import * as helper from './helper.js';
8
- import * as actor from './actor.js';
9
- import * as config from './config.js';
10
- import * as output from './output.js';
11
- import * as recorder from './recorder.js';
12
- import * as container from './container.js';
13
- import * as locator from './locator.js';
14
- import * as event from './event.js';
15
- import * as store from './store.js';
16
- import * as heal from './heal.js';
17
- import * as ai from './ai.js';
18
1
  /**
19
2
  * Index file for loading CodeceptJS programmatically.
20
3
  *
@@ -22,14 +5,30 @@ import * as ai from './ai.js';
22
5
  * @alias index
23
6
  * @namespace
24
7
  */
25
- /** @type {typeof CodeceptJS.Codecept} */
26
- export { _codecept as codecept };
27
- /** @type {typeof CodeceptJS.Codecept} */
28
- export { _codecept as Codecept };
29
- /** @type {typeof CodeceptJS.Helper} */
30
- export { helper as Helper };
31
- export { workers as Workers };
32
- export {
8
+ import codecept from './codecept.js'
9
+ import output from './output.js'
10
+ import container from './container.js'
11
+ import event from './event.js'
12
+ import recorder from './recorder.js'
13
+ import config from './config.js'
14
+ import actor from './actor.js'
15
+ import helper from './helper.js'
16
+ import pause from './pause.js'
17
+ import { within } from './effects.js'
18
+ import dataTable from './data/table.js'
19
+ import dataTableArgument from './data/dataTableArgument.js'
20
+ import store from './store.js'
21
+ import locator from './locator.js'
22
+ import heal from './heal.js'
23
+ import ai from './ai.js'
24
+ import Workers from './workers.js'
25
+ import Secret, { secret } from './secret.js'
26
+
27
+ export default {
28
+ /** @type {typeof CodeceptJS.Codecept} */
29
+ codecept,
30
+ /** @type {typeof CodeceptJS.Codecept} */
31
+ Codecept: codecept,
33
32
  /** @type {typeof CodeceptJS.output} */
34
33
  output,
35
34
  /** @type {typeof CodeceptJS.Container} */
@@ -44,6 +43,8 @@ export {
44
43
  actor,
45
44
  /** @type {typeof CodeceptJS.Helper} */
46
45
  helper,
46
+ /** @type {typeof CodeceptJS.Helper} */
47
+ Helper: helper,
47
48
  /** @type {typeof CodeceptJS.pause} */
48
49
  pause,
49
50
  /** @type {typeof CodeceptJS.within} */
@@ -56,7 +57,17 @@ export {
56
57
  store,
57
58
  /** @type {typeof CodeceptJS.Locator} */
58
59
  locator,
60
+
59
61
  heal,
60
62
  ai,
61
- workers,
62
- };
63
+
64
+ Workers,
65
+
66
+ /** @type {typeof CodeceptJS.Secret} */
67
+ Secret,
68
+ /** @type {typeof CodeceptJS.secret} */
69
+ secret,
70
+ }
71
+
72
+ // Named exports for ESM compatibility
73
+ export { codecept, output, container, event, recorder, config, actor, helper, pause, within, dataTable, dataTableArgument, store, locator, heal, ai, Workers, Secret, secret }
@@ -1,46 +1,53 @@
1
- import * as event from '../event.js';
2
- import container from '../container.js';
3
- import recorder from '../recorder.js';
4
- import { deepMerge, deepClone, ucfirst } from '../utils.js';
5
- import * as output from '../output.js';
1
+ import event from '../event.js'
2
+ import recorder from '../recorder.js'
3
+ import { deepMerge, deepClone, ucfirst } from '../utils.js'
4
+ import output from '../output.js'
6
5
 
7
6
  /**
8
7
  * Enable Helpers to listen to test events
9
8
  */
10
9
  export default function () {
11
- const helpers = container.helpers();
10
+ // Use global flag to prevent duplicate initialization across module re-imports
11
+ if (global.__codeceptConfigListenerInitialized) {
12
+ return
13
+ }
14
+ global.__codeceptConfigListenerInitialized = true
15
+
16
+ const helpers = global.container.helpers()
12
17
 
13
- enableDynamicConfigFor('suite');
14
- enableDynamicConfigFor('test');
18
+ enableDynamicConfigFor('suite')
19
+ enableDynamicConfigFor('test')
15
20
 
16
21
  function enableDynamicConfigFor(type) {
17
22
  event.dispatcher.on(event[type].before, (context = {}) => {
18
23
  function updateHelperConfig(helper, config) {
19
- const oldConfig = { ...helper.options };
24
+ const oldConfig = deepClone(helper.options)
20
25
  try {
21
- helper._setConfig(deepMerge(deepClone(oldConfig), config));
22
- debug(`[${ucfirst(type)} Config] ${helper.constructor.name} ${JSON.stringify(config)}`);
26
+ helper._setConfig(deepMerge(deepClone(oldConfig), config))
27
+ output.debug(`[${ucfirst(type)} Config] ${helper.constructor.name} ${JSON.stringify(config)}`)
23
28
  } catch (err) {
24
- recorder.throw(err);
25
- return;
29
+ recorder.throw(err)
30
+ return
31
+ }
32
+ const restoreCallback = () => {
33
+ helper._setConfig(oldConfig)
34
+ output.debug(`[${ucfirst(type)} Config] Reverted for ${helper.constructor.name}`)
26
35
  }
27
- event.dispatcher.once(event[type].after, () => {
28
- helper._setConfig(oldConfig);
29
- debug(`[${ucfirst(type)} Config] Reverted for ${helper.constructor.name}`);
30
- });
36
+ event.dispatcher.once(event[type].after, restoreCallback)
31
37
  }
32
38
 
33
39
  // change config
34
40
  if (context.config) {
35
41
  for (let name in context.config) {
36
- const config = context.config[name];
37
- if (name === '0') { // first helper
38
- name = Object.keys(helpers)[0];
42
+ const config = context.config[name]
43
+ if (name === '0') {
44
+ // first helper
45
+ name = Object.keys(helpers)[0]
39
46
  }
40
- const helper = helpers[name];
41
- updateHelperConfig(helper, config);
47
+ const helper = helpers[name]
48
+ updateHelperConfig(helper, config)
42
49
  }
43
50
  }
44
- });
51
+ })
45
52
  }
46
53
  }
@@ -0,0 +1,54 @@
1
+ import figures from 'figures'
2
+ import event from '../event.js'
3
+ import output from '../output.js'
4
+ import { searchWithFusejs } from '../utils.js'
5
+
6
+ export default function () {
7
+ let isEmptyRun = true
8
+
9
+ event.dispatcher.on(event.test.before, test => {
10
+ isEmptyRun = false
11
+ })
12
+
13
+ event.dispatcher.on(event.all.result, () => {
14
+ if (isEmptyRun) {
15
+ const mocha = global.container.mocha()
16
+
17
+ if (mocha.options.grep) {
18
+ output.print()
19
+ output.print('No tests found by pattern: ' + mocha.options.grep)
20
+
21
+ const allTests = []
22
+ mocha.suite.suites.forEach(suite => {
23
+ suite.tests.forEach(test => {
24
+ allTests.push(test.fullTitle())
25
+ })
26
+ })
27
+
28
+ const results = searchWithFusejs(allTests, mocha.options.grep.toString(), {
29
+ includeScore: true,
30
+ threshold: 0.6,
31
+ caseSensitive: false,
32
+ })
33
+
34
+ if (results.length > 0) {
35
+ output.print()
36
+ output.print('Maybe you wanted to run one of these tests?')
37
+ results.forEach(result => {
38
+ output.print(figures.checkboxOff, output.styles.log(result.item))
39
+ })
40
+
41
+ output.print()
42
+ output.print(output.styles.debug('To run the first test use the following command:'))
43
+ output.print(output.styles.bold('npx codeceptjs run --debug --grep "' + results[0].item + '"'))
44
+ }
45
+ }
46
+ if (process.env.CI && !process.env.DONT_FAIL_ON_EMPTY_RUN) {
47
+ output.print()
48
+ output.error('No tests were executed. Failing on CI to avoid false positives')
49
+ output.error('To disable this check, set `DONT_FAIL_ON_EMPTY_RUN` environment variable to true in CI config')
50
+ process.exitCode = 1
51
+ }
52
+ }
53
+ })
54
+ }