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.
- package/README.md +1 -3
- package/bin/codecept.js +51 -53
- package/bin/test-server.js +14 -3
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +15 -11
- package/lib/ai.js +72 -107
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +102 -75
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +62 -27
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +36 -29
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +24 -9
- package/lib/command/run.js +23 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +197 -114
- package/lib/config.js +124 -51
- package/lib/container.js +438 -87
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/element/WebElement.js +2 -2
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +11 -11
- package/lib/helper/ApiDataFactory.js +50 -19
- package/lib/helper/Appium.js +19 -27
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +25 -29
- package/lib/helper/Mochawesome.js +7 -4
- package/lib/helper/Playwright.js +902 -164
- package/lib/helper/Puppeteer.js +383 -76
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +268 -61
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightReactVueLocator.js +18 -9
- package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +29 -44
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +19 -12
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/enhancedGlobalRetry.js +6 -6
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +30 -14
- package/lib/listener/helpers.js +39 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/retryEnhancer.js +3 -3
- package/lib/listener/steps.js +8 -7
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +105 -62
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +59 -26
- package/lib/mocha/factory.js +78 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +56 -24
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -7
- package/lib/mocha/ui.js +28 -18
- package/lib/output.js +10 -8
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +28 -11
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/enhancedRetryFailedStep.js +6 -6
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/htmlReporter.js +724 -99
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +48 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -14
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/retryCoordinator.js +2 -2
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +7 -7
- package/lib/step/comment.js +2 -2
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +5 -5
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/template/heal.js +1 -1
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +17 -6
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils/mask_data.js +4 -10
- package/lib/utils.js +66 -64
- package/lib/workerStorage.js +17 -17
- package/lib/workers.js +214 -84
- package/package.json +41 -37
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +4 -3
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +5 -3
- package/typings/promiseBasedTypes.d.ts +4 -0
- package/typings/types.d.ts +4 -0
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/within.js +0 -90
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
| 🌐 Web | Playwright | [](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml) |
|
|
11
11
|
| 🌐 Web | Puppeteer | [](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml) |
|
|
12
12
|
| 🌐 Web | WebDriver | [](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml) |
|
|
13
|
-
| 🌐 Web | TestCafe | [](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml) |
|
|
14
13
|
| 📱 Mobile | Appium | [](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium_Android.yml) |
|
|
15
14
|
|
|
16
15
|
# CodeceptJS [](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,
|
|
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
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
138
|
+
.action(commandHandlerWithProperty('../lib/command/generate.js', 'pageObject'))
|
|
138
139
|
|
|
139
|
-
program
|
|
140
|
-
|
|
141
|
-
|
|
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:
|
|
147
|
-
.alias('
|
|
148
|
-
.description('Generates
|
|
149
|
-
.action(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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`)
|
package/bin/test-server.js
CHANGED
|
@@ -4,14 +4,20 @@
|
|
|
4
4
|
* Standalone test server script to replace json-server
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
104
|
+
if (this.config.model) return
|
|
128
105
|
|
|
129
|
-
const
|
|
130
|
-
No
|
|
106
|
+
const noModelErrorMessage = `
|
|
107
|
+
No model is set for AI assistant.
|
|
131
108
|
|
|
132
|
-
[!]
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
272
|
+
export default new AiAssistant()
|
package/lib/assert/empty.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
40
|
+
export { EmptinessAssertion as Assertion }
|
|
41
|
+
|
|
42
|
+
export const empty = subject => new EmptinessAssertion({ subject })
|