codeceptjs 3.7.6-beta.4 → 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 (191) hide show
  1. package/README.md +1 -3
  2. package/bin/codecept.js +51 -53
  3. package/bin/test-server.js +14 -3
  4. package/docs/webapi/click.mustache +5 -1
  5. package/lib/actor.js +15 -11
  6. package/lib/ai.js +72 -107
  7. package/lib/assert/empty.js +9 -8
  8. package/lib/assert/equal.js +15 -17
  9. package/lib/assert/error.js +2 -2
  10. package/lib/assert/include.js +9 -11
  11. package/lib/assert/throws.js +1 -1
  12. package/lib/assert/truth.js +8 -5
  13. package/lib/assert.js +18 -18
  14. package/lib/codecept.js +102 -75
  15. package/lib/colorUtils.js +48 -50
  16. package/lib/command/check.js +32 -27
  17. package/lib/command/configMigrate.js +11 -10
  18. package/lib/command/definitions.js +16 -10
  19. package/lib/command/dryRun.js +16 -16
  20. package/lib/command/generate.js +62 -27
  21. package/lib/command/gherkin/init.js +36 -38
  22. package/lib/command/gherkin/snippets.js +14 -14
  23. package/lib/command/gherkin/steps.js +21 -18
  24. package/lib/command/info.js +8 -8
  25. package/lib/command/init.js +36 -29
  26. package/lib/command/interactive.js +11 -10
  27. package/lib/command/list.js +10 -9
  28. package/lib/command/run-multiple/chunk.js +5 -5
  29. package/lib/command/run-multiple/collection.js +5 -5
  30. package/lib/command/run-multiple/run.js +3 -3
  31. package/lib/command/run-multiple.js +16 -13
  32. package/lib/command/run-rerun.js +6 -7
  33. package/lib/command/run-workers.js +24 -9
  34. package/lib/command/run.js +23 -8
  35. package/lib/command/utils.js +20 -18
  36. package/lib/command/workers/runTests.js +197 -114
  37. package/lib/config.js +124 -51
  38. package/lib/container.js +438 -87
  39. package/lib/data/context.js +6 -5
  40. package/lib/data/dataScenarioConfig.js +1 -1
  41. package/lib/data/dataTableArgument.js +1 -1
  42. package/lib/data/table.js +1 -1
  43. package/lib/effects.js +94 -10
  44. package/lib/element/WebElement.js +2 -2
  45. package/lib/els.js +11 -9
  46. package/lib/event.js +11 -10
  47. package/lib/globals.js +141 -0
  48. package/lib/heal.js +12 -12
  49. package/lib/helper/AI.js +11 -11
  50. package/lib/helper/ApiDataFactory.js +50 -19
  51. package/lib/helper/Appium.js +19 -27
  52. package/lib/helper/FileSystem.js +32 -12
  53. package/lib/helper/GraphQL.js +3 -3
  54. package/lib/helper/GraphQLDataFactory.js +4 -4
  55. package/lib/helper/JSONResponse.js +25 -29
  56. package/lib/helper/Mochawesome.js +7 -4
  57. package/lib/helper/Playwright.js +902 -164
  58. package/lib/helper/Puppeteer.js +383 -76
  59. package/lib/helper/REST.js +29 -12
  60. package/lib/helper/WebDriver.js +268 -61
  61. package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
  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 +18 -9
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
  71. package/lib/helper/extras/Popup.js +1 -1
  72. package/lib/helper/extras/React.js +29 -30
  73. package/lib/helper/network/actions.js +29 -44
  74. package/lib/helper/network/utils.js +76 -83
  75. package/lib/helper/scripts/blurElement.js +6 -6
  76. package/lib/helper/scripts/focusElement.js +6 -6
  77. package/lib/helper/scripts/highlightElement.js +9 -9
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -1
  80. package/lib/history.js +23 -20
  81. package/lib/hooks.js +10 -10
  82. package/lib/html.js +90 -100
  83. package/lib/index.js +48 -21
  84. package/lib/listener/config.js +19 -12
  85. package/lib/listener/emptyRun.js +6 -7
  86. package/lib/listener/enhancedGlobalRetry.js +6 -6
  87. package/lib/listener/exit.js +4 -3
  88. package/lib/listener/globalRetry.js +5 -5
  89. package/lib/listener/globalTimeout.js +30 -14
  90. package/lib/listener/helpers.js +39 -14
  91. package/lib/listener/mocha.js +3 -4
  92. package/lib/listener/result.js +4 -5
  93. package/lib/listener/retryEnhancer.js +3 -3
  94. package/lib/listener/steps.js +8 -7
  95. package/lib/listener/store.js +3 -3
  96. package/lib/locator.js +213 -192
  97. package/lib/mocha/asyncWrapper.js +105 -62
  98. package/lib/mocha/bdd.js +99 -13
  99. package/lib/mocha/cli.js +59 -26
  100. package/lib/mocha/factory.js +78 -19
  101. package/lib/mocha/featureConfig.js +1 -1
  102. package/lib/mocha/gherkin.js +56 -24
  103. package/lib/mocha/hooks.js +12 -3
  104. package/lib/mocha/index.js +13 -4
  105. package/lib/mocha/inject.js +22 -5
  106. package/lib/mocha/scenarioConfig.js +2 -2
  107. package/lib/mocha/suite.js +9 -2
  108. package/lib/mocha/test.js +10 -7
  109. package/lib/mocha/ui.js +28 -18
  110. package/lib/output.js +10 -8
  111. package/lib/parser.js +44 -44
  112. package/lib/pause.js +15 -16
  113. package/lib/plugin/analyze.js +19 -12
  114. package/lib/plugin/auth.js +20 -21
  115. package/lib/plugin/autoDelay.js +12 -8
  116. package/lib/plugin/coverage.js +28 -11
  117. package/lib/plugin/customLocator.js +3 -3
  118. package/lib/plugin/customReporter.js +3 -2
  119. package/lib/plugin/enhancedRetryFailedStep.js +6 -6
  120. package/lib/plugin/heal.js +14 -9
  121. package/lib/plugin/htmlReporter.js +724 -99
  122. package/lib/plugin/pageInfo.js +10 -10
  123. package/lib/plugin/pauseOnFail.js +4 -3
  124. package/lib/plugin/retryFailedStep.js +48 -5
  125. package/lib/plugin/screenshotOnFail.js +75 -37
  126. package/lib/plugin/stepByStepReport.js +14 -14
  127. package/lib/plugin/stepTimeout.js +4 -3
  128. package/lib/plugin/subtitles.js +6 -5
  129. package/lib/recorder.js +33 -14
  130. package/lib/rerun.js +69 -26
  131. package/lib/result.js +4 -4
  132. package/lib/retryCoordinator.js +2 -2
  133. package/lib/secret.js +18 -17
  134. package/lib/session.js +95 -89
  135. package/lib/step/base.js +7 -7
  136. package/lib/step/comment.js +2 -2
  137. package/lib/step/config.js +1 -1
  138. package/lib/step/func.js +3 -3
  139. package/lib/step/helper.js +3 -3
  140. package/lib/step/meta.js +5 -5
  141. package/lib/step/record.js +11 -11
  142. package/lib/step/retry.js +3 -3
  143. package/lib/step/section.js +3 -3
  144. package/lib/step.js +7 -10
  145. package/lib/steps.js +9 -5
  146. package/lib/store.js +1 -1
  147. package/lib/template/heal.js +1 -1
  148. package/lib/template/prompts/generatePageObject.js +31 -0
  149. package/lib/template/prompts/healStep.js +13 -0
  150. package/lib/template/prompts/writeStep.js +9 -0
  151. package/lib/test-server.js +17 -6
  152. package/lib/timeout.js +1 -7
  153. package/lib/transform.js +8 -8
  154. package/lib/translation.js +32 -18
  155. package/lib/utils/mask_data.js +4 -10
  156. package/lib/utils.js +66 -64
  157. package/lib/workerStorage.js +17 -17
  158. package/lib/workers.js +214 -84
  159. package/package.json +41 -37
  160. package/translations/de-DE.js +2 -2
  161. package/translations/fr-FR.js +2 -2
  162. package/translations/index.js +23 -10
  163. package/translations/it-IT.js +2 -2
  164. package/translations/ja-JP.js +2 -2
  165. package/translations/nl-NL.js +2 -2
  166. package/translations/pl-PL.js +2 -2
  167. package/translations/pt-BR.js +2 -2
  168. package/translations/ru-RU.js +2 -2
  169. package/translations/utils.js +4 -3
  170. package/translations/zh-CN.js +2 -2
  171. package/translations/zh-TW.js +2 -2
  172. package/typings/index.d.ts +5 -3
  173. package/typings/promiseBasedTypes.d.ts +4 -0
  174. package/typings/types.d.ts +4 -0
  175. package/lib/helper/Nightmare.js +0 -1486
  176. package/lib/helper/Protractor.js +0 -1840
  177. package/lib/helper/TestCafe.js +0 -1391
  178. package/lib/helper/clientscripts/nightmare.js +0 -213
  179. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  180. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  181. package/lib/plugin/allure.js +0 -15
  182. package/lib/plugin/autoLogin.js +0 -5
  183. package/lib/plugin/commentStep.js +0 -141
  184. package/lib/plugin/eachElement.js +0 -127
  185. package/lib/plugin/fakerTransform.js +0 -49
  186. package/lib/plugin/retryTo.js +0 -16
  187. package/lib/plugin/selenoid.js +0 -364
  188. package/lib/plugin/standardActingHelpers.js +0 -6
  189. package/lib/plugin/tryTo.js +0 -16
  190. package/lib/plugin/wdio.js +0 -247
  191. package/lib/within.js +0 -90
package/README.md CHANGED
@@ -10,7 +10,6 @@
10
10
  | 🌐 Web | Playwright | [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml) |
11
11
  | 🌐 Web | Puppeteer | [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml) |
12
12
  | 🌐 Web | WebDriver | [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml) |
13
- | 🌐 Web | TestCafe | [![TestCafe Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml) |
14
13
  | 📱 Mobile | Appium | [![Appium Tests - Android](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium_Android.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium_Android.yml) |
15
14
 
16
15
  # CodeceptJS [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua)
@@ -43,7 +42,6 @@ CodeceptJS uses **Helper** modules to provide actions to `I` object. Currently,
43
42
  - [**Playwright**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Playwright.md) - is a Node library to automate the Chromium, WebKit and Firefox browsers with a single API.
44
43
  - [**Puppeteer**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Puppeteer.md) - uses Google Chrome's Puppeteer for fast headless testing.
45
44
  - [**WebDriver**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) - uses [webdriverio](http://webdriver.io/) to run tests via WebDriver or Devtools protocol.
46
- - [**TestCafe**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/TestCafe.md) - cheap and fast cross-browser test automation.
47
45
  - [**Appium**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Appium.md) - for **mobile testing** with Appium
48
46
  - [**Detox**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Detox.md) - This is a wrapper on top of Detox library, aimed to unify testing experience for CodeceptJS framework. Detox provides a grey box testing for mobile applications, playing especially well for React Native apps.
49
47
 
@@ -53,7 +51,7 @@ And more to come...
53
51
 
54
52
  CodeceptJS is a successor of [Codeception](http://codeception.com), a popular full-stack testing framework for PHP.
55
53
  With CodeceptJS your scenario-driven functional and acceptance tests will be as simple and clean as they can be.
56
- You don't need to worry about asynchronous nature of NodeJS or about various APIs of Playwright, Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
54
+ You don't need to worry about asynchronous nature of NodeJS or about various APIs of Playwright, Selenium, Puppeteer, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
57
55
 
58
56
  ## Features
59
57
 
package/bin/codecept.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- const program = require('commander')
3
- const Codecept = require('../lib/codecept')
4
- const { print, error } = require('../lib/output')
5
- const { printError } = require('../lib/command/utils')
2
+ import { Command } from 'commander'
3
+ const program = new Command()
4
+ import Codecept from '../lib/codecept.js'
5
+ import output from '../lib/output.js'
6
+ const { print, error } = output
7
+ import { printError } from '../lib/command/utils.js'
6
8
 
7
9
  const commandFlags = {
8
10
  ai: {
@@ -42,6 +44,23 @@ const errorHandler =
42
44
  }
43
45
  }
44
46
 
47
+ const dynamicImport = async modulePath => {
48
+ const module = await import(modulePath)
49
+ return module.default || module
50
+ }
51
+
52
+ const commandHandler = modulePath =>
53
+ errorHandler(async (...args) => {
54
+ const handler = await dynamicImport(modulePath)
55
+ return handler(...args)
56
+ })
57
+
58
+ const commandHandlerWithProperty = (modulePath, property) =>
59
+ errorHandler(async (...args) => {
60
+ const module = await dynamicImport(modulePath)
61
+ return module[property](...args)
62
+ })
63
+
45
64
  if (process.versions.node && process.versions.node.split('.') && process.versions.node.split('.')[0] < 12) {
46
65
  error('NodeJS >= 12 is required to run.')
47
66
  print()
@@ -53,22 +72,16 @@ if (process.versions.node && process.versions.node.split('.') && process.version
53
72
  program.usage('<command> [options]')
54
73
  program.version(Codecept.version())
55
74
 
56
- program
57
- .command('init [path]')
58
- .description('Creates dummy config in current dir or [path]')
59
- .action(errorHandler(require('../lib/command/init')))
75
+ program.command('init [path]').description('Creates dummy config in current dir or [path]').action(commandHandler('../lib/command/init.js'))
60
76
 
61
77
  program
62
78
  .command('check')
63
79
  .option(commandFlags.config.flag, commandFlags.config.description)
64
80
  .description('Checks configuration and environment before running tests')
65
81
  .option('-t, --timeout [ms]', 'timeout for checks in ms, 50000 by default')
66
- .action(errorHandler(require('../lib/command/check')))
82
+ .action(commandHandler('../lib/command/check.js'))
67
83
 
68
- program
69
- .command('migrate [path]')
70
- .description('Migrate json config to js config in current dir or [path]')
71
- .action(errorHandler(require('../lib/command/configMigrate')))
84
+ program.command('migrate [path]').description('Migrate json config to js config in current dir or [path]').action(commandHandler('../lib/command/configMigrate.js'))
72
85
 
73
86
  program
74
87
  .command('shell [path]')
@@ -78,34 +91,30 @@ program
78
91
  .option(commandFlags.profile.flag, commandFlags.profile.description)
79
92
  .option(commandFlags.ai.flag, commandFlags.ai.description)
80
93
  .option(commandFlags.config.flag, commandFlags.config.description)
81
- .action(errorHandler(require('../lib/command/interactive')))
94
+ .action(commandHandler('../lib/command/interactive.js'))
82
95
 
83
- program
84
- .command('list [path]')
85
- .alias('l')
86
- .description('List all actions for I.')
87
- .action(errorHandler(require('../lib/command/list')))
96
+ program.command('list [path]').alias('l').description('List all actions for I.').action(commandHandler('../lib/command/list.js'))
88
97
 
89
98
  program
90
99
  .command('def [path]')
91
100
  .description('Generates TypeScript definitions for all I actions.')
92
101
  .option(commandFlags.config.flag, commandFlags.config.description)
93
102
  .option('-o, --output [folder]', 'target folder to paste definitions')
94
- .action(errorHandler(require('../lib/command/definitions')))
103
+ .action(commandHandler('../lib/command/definitions.js'))
95
104
 
96
105
  program
97
106
  .command('gherkin:init [path]')
98
107
  .alias('bdd:init')
99
108
  .description('Prepare CodeceptJS to run feature files.')
100
109
  .option(commandFlags.config.flag, commandFlags.config.description)
101
- .action(errorHandler(require('../lib/command/gherkin/init')))
110
+ .action(commandHandler('../lib/command/gherkin/init.js'))
102
111
 
103
112
  program
104
113
  .command('gherkin:steps [path]')
105
114
  .alias('bdd:steps')
106
115
  .description('Prints all defined gherkin steps.')
107
116
  .option(commandFlags.config.flag, commandFlags.config.description)
108
- .action(errorHandler(require('../lib/command/gherkin/steps')))
117
+ .action(commandHandler('../lib/command/gherkin/steps.js'))
109
118
 
110
119
  program
111
120
  .command('gherkin:snippets [path]')
@@ -115,38 +124,28 @@ program
115
124
  .option(commandFlags.config.flag, commandFlags.config.description)
116
125
  .option('--feature [file]', 'feature files(s) to scan')
117
126
  .option('--path [file]', 'file in which to place the new snippets')
118
- .action(errorHandler(require('../lib/command/gherkin/snippets')))
127
+ .action(commandHandler('../lib/command/gherkin/snippets.js'))
119
128
 
120
- program
121
- .command('generate:test [path]')
122
- .alias('gt')
123
- .description('Generates an empty test')
124
- .action(errorHandler(require('../lib/command/generate').test))
129
+ program.command('generate:test [path]').alias('gt').description('Generates an empty test').action(commandHandlerWithProperty('../lib/command/generate.js', 'test'))
125
130
 
126
- program
127
- .command('generate:pageobject [path]')
128
- .alias('gpo')
129
- .description('Generates an empty page object')
130
- .action(errorHandler(require('../lib/command/generate').pageObject))
131
+ program.command('generate:pageobject [path]').alias('gpo').description('Generates an empty page object').action(commandHandlerWithProperty('../lib/command/generate.js', 'pageObject'))
131
132
 
132
133
  program
133
134
  .command('generate:object [path]')
134
135
  .alias('go')
135
136
  .option('--type, -t [kind]', 'type of object to be created')
136
137
  .description('Generates an empty support object (page/step/fragment)')
137
- .action(errorHandler(require('../lib/command/generate').pageObject))
138
+ .action(commandHandlerWithProperty('../lib/command/generate.js', 'pageObject'))
138
139
 
139
- program
140
- .command('generate:helper [path]')
141
- .alias('gh')
142
- .description('Generates a new helper')
143
- .action(errorHandler(require('../lib/command/generate').helper))
140
+ program.command('generate:helper [path]').alias('gh').description('Generates a new helper').action(commandHandlerWithProperty('../lib/command/generate.js', 'helper'))
141
+
142
+ program.command('generate:heal [path]').alias('gr').description('Generates basic heal recipes').action(commandHandlerWithProperty('../lib/command/generate.js', 'heal'))
144
143
 
145
144
  program
146
- .command('generate:heal [path]')
147
- .alias('gr')
148
- .description('Generates basic heal recipes')
149
- .action(errorHandler(require('../lib/command/generate').heal))
145
+ .command('generate:prompt <promptName> [path]')
146
+ .alias('gp')
147
+ .description('Generates AI prompt template (writeStep, healStep, generatePageObject)')
148
+ .action(commandHandlerWithProperty('../lib/command/generate.js', 'prompt'))
150
149
 
151
150
  program
152
151
  .command('run [test]')
@@ -187,7 +186,7 @@ program
187
186
  .option('--recursive', 'include sub directories')
188
187
  .option('--trace', 'trace function calls')
189
188
  .option('--child <string>', 'option for child processes')
190
- .action(errorHandler(require('../lib/command/run')))
189
+ .action(commandHandler('../lib/command/run.js'))
191
190
 
192
191
  program
193
192
  .command('run-workers <workers> [selectedRuns...]')
@@ -207,7 +206,7 @@ program
207
206
  .option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated')
208
207
  .option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
209
208
  .option('-R, --reporter <name>', 'specify the reporter to use')
210
- .action(errorHandler(require('../lib/command/run-workers')))
209
+ .action(commandHandler('../lib/command/run-workers.js'))
211
210
 
212
211
  program
213
212
  .command('run-multiple [suites...]')
@@ -233,13 +232,9 @@ program
233
232
  // mocha options
234
233
  .option('--colors', 'force enabling of colors')
235
234
 
236
- .action(errorHandler(require('../lib/command/run-multiple')))
235
+ .action(commandHandler('../lib/command/run-multiple.js'))
237
236
 
238
- program
239
- .command('info [path]')
240
- .description('Print debugging information concerning the local environment')
241
- .option('-c, --config', 'your config file path')
242
- .action(errorHandler(require('../lib/command/info')))
237
+ program.command('info [path]').description('Print debugging information concerning the local environment').option('-c, --config', 'your config file path').action(commandHandler('../lib/command/info.js'))
243
238
 
244
239
  program
245
240
  .command('dry-run [test]')
@@ -256,7 +251,7 @@ program
256
251
  .option(commandFlags.steps.flag, commandFlags.steps.description)
257
252
  .option(commandFlags.verbose.flag, commandFlags.verbose.description)
258
253
  .option(commandFlags.debug.flag, commandFlags.debug.description)
259
- .action(errorHandler(require('../lib/command/dryRun')))
254
+ .action(commandHandler('../lib/command/dryRun.js'))
260
255
 
261
256
  program
262
257
  .command('run-rerun [test]')
@@ -294,7 +289,10 @@ program
294
289
  .option('--trace', 'trace function calls')
295
290
  .option('--child <string>', 'option for child processes')
296
291
 
297
- .action(require('../lib/command/run-rerun'))
292
+ .action(async (...args) => {
293
+ const runRerun = await dynamicImport('../lib/command/run-rerun.js')
294
+ return runRerun(...args)
295
+ })
298
296
 
299
297
  program.on('command:*', cmd => {
300
298
  console.log(`\nUnknown command ${cmd}\n`)
@@ -4,14 +4,20 @@
4
4
  * Standalone test server script to replace json-server
5
5
  */
6
6
 
7
- const path = require('path')
8
- const TestServer = require('../lib/test-server')
7
+ import path from 'path'
8
+ import { fileURLToPath } from 'url'
9
+ import { dirname } from 'path'
10
+ import TestServer from '../lib/test-server.js'
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = dirname(__filename)
9
14
 
10
15
  // Parse command line arguments
11
16
  const args = process.argv.slice(2)
12
17
  let dbFile = path.join(__dirname, '../test/data/rest/db.json')
13
18
  let port = 8010
14
19
  let host = '0.0.0.0'
20
+ let readOnly = false
15
21
 
16
22
  // Simple argument parsing
17
23
  for (let i = 0; i < args.length; i++) {
@@ -21,15 +27,20 @@ for (let i = 0; i < args.length; i++) {
21
27
  port = parseInt(args[++i])
22
28
  } else if (arg === '--host') {
23
29
  host = args[++i]
30
+ } else if (arg === '--read-only' || arg === '-r') {
31
+ readOnly = true
24
32
  } else if (!arg.startsWith('-')) {
25
33
  dbFile = path.resolve(arg)
26
34
  }
27
35
  }
28
36
 
29
37
  // Create and start server
30
- const server = new TestServer({ port, host, dbFile })
38
+ const server = new TestServer({ port, host, dbFile, readOnly })
31
39
 
32
40
  console.log(`Starting test server with db file: ${dbFile}`)
41
+ if (readOnly) {
42
+ console.log('Running in READ-ONLY mode - changes will not be persisted to disk')
43
+ }
33
44
 
34
45
  server
35
46
  .start()
@@ -3,9 +3,13 @@ If a fuzzy locator is given, the page will be searched for a button, link, or im
3
3
  For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
4
4
  For images, the "alt" attribute and inner text of any parent links are searched.
5
5
 
6
+ If no locator is provided, defaults to clicking the body element (`'//body'`).
7
+
6
8
  The second parameter is a context (CSS or XPath locator) to narrow the search.
7
9
 
8
10
  ```js
11
+ // click body element (default)
12
+ I.click();
9
13
  // simple link
10
14
  I.click('Logout');
11
15
  // button of form
@@ -20,6 +24,6 @@ I.click('Logout', '#nav');
20
24
  I.click({css: 'nav a.login'});
21
25
  ```
22
26
 
23
- @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
27
+ @param {CodeceptJS.LocatorOrString} [locator='//body'] (optional, `'//body'` by default) clickable link or button located by text, or any element located by CSS|XPath|strict locator.
24
28
  @param {?CodeceptJS.LocatorOrString | null} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
25
29
  @returns {void} automatically synchronized promise through #recorder
package/lib/actor.js CHANGED
@@ -1,12 +1,12 @@
1
- const Step = require('./step')
2
- const MetaStep = require('./step/meta')
3
- const recordStep = require('./step/record')
4
- const container = require('./container')
5
- const { methodsOfObject } = require('./utils')
6
- const { TIMEOUT_ORDER } = require('./timeout')
7
- const event = require('./event')
8
- const store = require('./store')
9
- const output = require('./output')
1
+ import Step, { MetaStep } from './step.js'
2
+ import recordStep from './step/record.js'
3
+ import retryStep from './step/retry.js'
4
+ import { methodsOfObject } from './utils.js'
5
+ import { TIMEOUT_ORDER } from './timeout.js'
6
+ import event from './event.js'
7
+ import store from './store.js'
8
+ import output from './output.js'
9
+ import Container from './container.js'
10
10
 
11
11
  /**
12
12
  * @interface
@@ -59,7 +59,6 @@ class Actor {
59
59
  */
60
60
  retry(opts) {
61
61
  console.log('I.retry() is deprecated, use step.retry() instead')
62
- const retryStep = require('./step/retry')
63
62
  retryStep(opts)
64
63
  return this
65
64
  }
@@ -71,7 +70,12 @@ class Actor {
71
70
  * Wraps helper methods into promises.
72
71
  * @ignore
73
72
  */
74
- module.exports = function (obj = {}) {
73
+ export default function (obj = {}, container) {
74
+ // Use global container if none provided
75
+ if (!container) {
76
+ container = Container
77
+ }
78
+
75
79
  const actor = container.actor() || new Actor()
76
80
 
77
81
  // load all helpers once container initialized
package/lib/ai.js CHANGED
@@ -1,7 +1,14 @@
1
- const debug = require('debug')('codeceptjs:ai')
2
- const output = require('./output')
3
- const event = require('./event')
4
- const { removeNonInteractiveElements, minifyHtml, splitByChunks } = require('./html')
1
+ import debugModule from 'debug'
2
+ const debug = debugModule('codeceptjs:ai')
3
+ import output from './output.js'
4
+ import event from './event.js'
5
+ import { removeNonInteractiveElements, minifyHtml, splitByChunks } from './html.js'
6
+ import { generateText } from 'ai'
7
+ import { fileURLToPath } from 'url'
8
+ import path from 'path'
9
+ import { fileExists } from './utils.js'
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
5
12
 
6
13
  const defaultHtmlConfig = {
7
14
  maxLength: 50000,
@@ -10,62 +17,31 @@ const defaultHtmlConfig = {
10
17
  html: {},
11
18
  }
12
19
 
13
- const defaultPrompts = {
14
- writeStep: (html, input) => [
15
- {
16
- role: 'user',
17
- content: `I am test engineer writing test in CodeceptJS
18
- I have opened web page and I want to use CodeceptJS to ${input} on this page
19
- Provide me valid CodeceptJS code to accomplish it
20
- Use only locators from this HTML: \n\n${html}`,
21
- },
22
- ],
23
-
24
- healStep: (html, { step, error, prevSteps }) => {
25
- return [
26
- {
27
- role: 'user',
28
- content: `As a test automation engineer I am testing web application using CodeceptJS.
29
- I want to heal a test that fails. Here is the list of executed steps: ${prevSteps.map(s => s.toString()).join(', ')}
30
- Propose how to adjust ${step.toCode()} step to fix the test.
31
- Use locators in order of preference: semantic locator by text, CSS, XPath. Use codeblocks marked with \`\`\`
32
- Here is the error message: ${error.message}
33
- Here is HTML code of a page where the failure has happened: \n\n${html}`,
34
- },
35
- ]
36
- },
37
-
38
- generatePageObject: (html, extraPrompt = '', rootLocator = null) => [
39
- {
40
- role: 'user',
41
- content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
42
- Here is an sample page object:
43
-
44
- const { I } = inject();
45
-
46
- module.exports = {
47
-
48
- // setting locators
49
- element1: '#selector',
50
- element2: '.selector',
51
- element3: locate().withText('text'),
52
-
53
- // seting methods
54
- doSomethingOnPage(params) {
55
- // ...
56
- },
57
- }
58
-
59
- I want to generate a Page Object for the page I provide.
60
- Write JavaScript code in similar manner to list all locators on the page.
61
- Use locators in order of preference: by text (use locate().withText()), label, CSS, XPath.
62
- Avoid TailwindCSS, Bootstrap or React style formatting classes in locators.
63
- Add methods to to interact with page when needed.
64
- ${extraPrompt}
65
- ${rootLocator ? `All provided elements are inside '${rootLocator}'. Declare it as root variable and for every locator use locate(...).inside(root)` : ''}
66
- Add only locators from this HTML: \n\n${html}`,
67
- },
68
- ],
20
+ async function loadPrompts() {
21
+ const prompts = {}
22
+ const promptNames = ['writeStep', 'healStep', 'generatePageObject']
23
+
24
+ for (const name of promptNames) {
25
+ let promptPath
26
+
27
+ if (global.codecept_dir) {
28
+ promptPath = path.join(global.codecept_dir, `prompts/${name}.js`)
29
+ }
30
+
31
+ if (!promptPath || !fileExists(promptPath)) {
32
+ promptPath = path.join(__dirname, `template/prompts/${name}.js`)
33
+ }
34
+
35
+ try {
36
+ const module = await import(promptPath)
37
+ prompts[name] = module.default || module
38
+ debug(`Loaded prompt ${name} from ${promptPath}`)
39
+ } catch (err) {
40
+ debug(`Failed to load prompt ${name}:`, err.message)
41
+ }
42
+ }
43
+
44
+ return prompts
69
45
  }
70
46
 
71
47
  class AiAssistant {
@@ -77,7 +53,7 @@ class AiAssistant {
77
53
  this.connectToEvents()
78
54
  }
79
55
 
80
- enable(config = {}) {
56
+ async enable(config = {}) {
81
57
  debug('Enabling AI assistant')
82
58
  this.isEnabled = true
83
59
 
@@ -85,7 +61,9 @@ class AiAssistant {
85
61
 
86
62
  this.config = Object.assign(this.config, aiConfig)
87
63
  this.htmlConfig = Object.assign(defaultHtmlConfig, html)
88
- this.prompts = Object.assign(defaultPrompts, prompts)
64
+
65
+ const loadedPrompts = await loadPrompts()
66
+ this.prompts = Object.assign(loadedPrompts, prompts || {})
89
67
 
90
68
  debug('Config', this.config)
91
69
  }
@@ -95,9 +73,8 @@ class AiAssistant {
95
73
  this.isEnabled = false
96
74
  this.config = {
97
75
  maxTokens: 1000000,
98
- request: null,
76
+ model: null,
99
77
  response: parseCodeBlocks,
100
- // lets limit token usage to 1M
101
78
  }
102
79
  this.minifiedHtml = null
103
80
  this.response = null
@@ -113,41 +90,44 @@ class AiAssistant {
113
90
  if (this.isEnabled && this.numTokens > 0) {
114
91
  const numTokensK = Math.ceil(this.numTokens / 1000)
115
92
  const maxTokensK = Math.ceil(this.config.maxTokens / 1000)
116
- output.print(`AI assistant took ${this.totalTime}s and used ~${numTokensK}K input tokens. Tokens limit: ${maxTokensK}K`)
93
+ output.print(`AI assistant took ${this.totalTime}s and used ${numTokensK}K tokens. Tokens limit: ${maxTokensK}K`)
117
94
  }
118
95
  })
119
96
  }
120
97
 
121
- checkRequestFn() {
98
+ checkModel() {
122
99
  if (!this.isEnabled) {
123
100
  debug('AI assistant is disabled')
124
101
  return
125
102
  }
126
103
 
127
- if (this.config.request) return
104
+ if (this.config.model) return
128
105
 
129
- const noRequestErrorMessage = `
130
- No request function is set for AI assistant.
106
+ const noModelErrorMessage = `
107
+ No model is set for AI assistant.
131
108
 
132
- [!] AI request was decoupled from CodeceptJS. To connect to OpenAI or other AI service.
133
- Please implement your own request function and set it in the config.
109
+ [!] Please configure AI model using Vercel AI SDK providers.
134
110
 
135
111
  Example (connect to OpenAI):
136
112
 
113
+ import { openai } from '@ai-sdk/openai';
114
+
137
115
  ai: {
138
- request: async (messages) => {
139
- const OpenAI = require('openai');
140
- const openai = new OpenAI({ apiKey: process.env['OPENAI_API_KEY'] })
141
- const response = await openai.chat.completions.create({
142
- model: 'gpt-4o-mini',
143
- messages,
144
- });
145
- return response?.data?.choices[0]?.message?.content;
146
- }
116
+ model: openai('gpt-4o-mini')
147
117
  }
118
+
119
+ Example (connect to Anthropic):
120
+
121
+ import { anthropic } from '@ai-sdk/anthropic';
122
+
123
+ ai: {
124
+ model: anthropic('claude-3-5-sonnet-20241022')
125
+ }
126
+
127
+ See https://ai-sdk.dev/docs/foundations/providers-and-models for all providers.
148
128
  `.trim()
149
129
 
150
- throw new Error(noRequestErrorMessage)
130
+ throw new Error(noModelErrorMessage)
151
131
  }
152
132
 
153
133
  async setHtmlContext(html) {
@@ -171,28 +151,32 @@ class AiAssistant {
171
151
  if (!this.isEnabled) return ''
172
152
 
173
153
  try {
174
- this.checkRequestFn()
154
+ this.checkModel()
175
155
  debug('Request', messages)
176
156
 
177
157
  this.response = null
178
158
 
179
- this.calculateTokens(messages)
180
159
  const startTime = process.hrtime()
181
- this.response = await this.config.request(messages)
160
+ const result = await generateText({
161
+ model: this.config.model,
162
+ messages,
163
+ })
182
164
  const endTime = process.hrtime(startTime)
183
165
  const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9
184
166
 
167
+ this.response = result.text
168
+ this.numTokens += result.usage.totalTokens
169
+
185
170
  this.totalTime += Math.round(executionTimeInSeconds)
186
171
  debug('AI response time', executionTimeInSeconds)
187
172
  debug('Response', this.response)
173
+ debug('Usage', result.usage)
188
174
  this.stopWhenReachingTokensLimit()
189
175
  return this.response
190
176
  } catch (err) {
191
- debug(err.response)
177
+ debug(err)
192
178
  output.print('')
193
179
  output.error(`AI service error: ${err.message}`)
194
- if (err?.response?.data?.error?.code) output.error(err?.response?.data?.error?.code)
195
- if (err?.response?.data?.error?.message) output.error(err?.response?.data?.error?.message)
196
180
  this.stopWhenReachingTokensLimit()
197
181
  return ''
198
182
  }
@@ -231,25 +215,6 @@ class AiAssistant {
231
215
  return this.config.response(response)
232
216
  }
233
217
 
234
- calculateTokens(messages) {
235
- // we implement naive approach for calculating tokens with no extra requests
236
- // this approach was tested via https://platform.openai.com/tokenizer
237
- // we need it to display current tokens usage so users could analyze effectiveness of AI
238
-
239
- const inputString = messages
240
- .map(m => m.content)
241
- .join(' ')
242
- .trim()
243
- const numWords = (inputString.match(/[^\s\-:=]+/g) || []).length
244
-
245
- // 2.5 token is constant for average HTML input
246
- const tokens = numWords * 2.5
247
-
248
- this.numTokens += tokens
249
-
250
- return tokens
251
- }
252
-
253
218
  stopWhenReachingTokensLimit() {
254
219
  if (this.numTokens < this.config.maxTokens) return
255
220
 
@@ -304,4 +269,4 @@ function parseCodeBlocks(response) {
304
269
  return modifiedSnippets.filter(snippet => !!snippet)
305
270
  }
306
271
 
307
- module.exports = new AiAssistant()
272
+ export default new AiAssistant()
@@ -1,7 +1,9 @@
1
- const Assertion = require('../assert')
2
- const AssertionFailedError = require('./error')
3
- const { template } = require('../utils')
4
- const output = require('../output')
1
+ import assertionModule from '../assert.js'
2
+ const Assertion = assertionModule.default || assertionModule
3
+ import AssertionFailedError from './error.js'
4
+ import { template } from '../utils.js'
5
+ import outputModule from '../output.js'
6
+ const output = outputModule.default || outputModule
5
7
 
6
8
  class EmptinessAssertion extends Assertion {
7
9
  constructor(params) {
@@ -35,7 +37,6 @@ class EmptinessAssertion extends Assertion {
35
37
  }
36
38
  }
37
39
 
38
- module.exports = {
39
- Assertion: EmptinessAssertion,
40
- empty: subject => new EmptinessAssertion({ subject }),
41
- }
40
+ export { EmptinessAssertion as Assertion }
41
+
42
+ export const empty = subject => new EmptinessAssertion({ subject })