codeceptjs 4.0.0-beta.2 → 4.0.0-beta.21
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 +133 -120
- package/bin/codecept.js +107 -96
- package/bin/test-server.js +64 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +73 -103
- package/lib/ai.js +159 -188
- package/lib/assert/empty.js +22 -24
- package/lib/assert/equal.js +30 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +43 -48
- package/lib/assert/throws.js +11 -11
- package/lib/assert/truth.js +22 -22
- package/lib/assert.js +20 -18
- package/lib/codecept.js +262 -162
- package/lib/colorUtils.js +50 -52
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +56 -51
- package/lib/command/definitions.js +96 -109
- package/lib/command/dryRun.js +77 -79
- package/lib/command/generate.js +234 -194
- package/lib/command/gherkin/init.js +42 -33
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +20 -17
- package/lib/command/info.js +74 -38
- package/lib/command/init.js +301 -290
- package/lib/command/interactive.js +41 -32
- package/lib/command/list.js +28 -27
- package/lib/command/run-multiple/chunk.js +51 -48
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +5 -1
- package/lib/command/run-multiple.js +97 -97
- package/lib/command/run-rerun.js +19 -25
- package/lib/command/run-workers.js +68 -92
- package/lib/command/run.js +39 -27
- package/lib/command/utils.js +80 -64
- package/lib/command/workers/runTests.js +388 -226
- package/lib/config.js +109 -50
- package/lib/container.js +765 -261
- package/lib/data/context.js +60 -61
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +32 -32
- package/lib/data/table.js +22 -22
- package/lib/effects.js +307 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +160 -0
- package/lib/event.js +173 -163
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -85
- package/lib/helper/AI.js +131 -41
- package/lib/helper/ApiDataFactory.js +107 -75
- package/lib/helper/Appium.js +542 -404
- package/lib/helper/FileSystem.js +100 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +52 -52
- package/lib/helper/JSONResponse.js +126 -88
- package/lib/helper/Mochawesome.js +54 -29
- package/lib/helper/Playwright.js +2547 -1316
- package/lib/helper/Puppeteer.js +1578 -1181
- package/lib/helper/REST.js +209 -68
- package/lib/helper/WebDriver.js +1482 -1342
- 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 +17 -8
- package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +27 -28
- package/lib/helper/network/actions.js +36 -42
- package/lib/helper/network/utils.js +78 -84
- package/lib/helper/scripts/blurElement.js +5 -5
- package/lib/helper/scripts/focusElement.js +5 -5
- package/lib/helper/scripts/highlightElement.js +8 -8
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -3
- package/lib/history.js +23 -19
- package/lib/hooks.js +8 -8
- package/lib/html.js +94 -104
- package/lib/index.js +38 -27
- package/lib/listener/config.js +30 -23
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/exit.js +16 -18
- package/lib/listener/globalRetry.js +70 -0
- package/lib/listener/globalTimeout.js +181 -0
- package/lib/listener/helpers.js +76 -51
- package/lib/listener/mocha.js +10 -11
- package/lib/listener/result.js +11 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +71 -59
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +214 -197
- package/lib/mocha/asyncWrapper.js +274 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +163 -0
- package/lib/mocha/featureConfig.js +89 -0
- package/lib/mocha/gherkin.js +231 -0
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +184 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +242 -0
- package/lib/output.js +141 -71
- package/lib/parser.js +54 -44
- package/lib/pause.js +173 -145
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +178 -79
- package/lib/plugin/autoDelay.js +36 -40
- package/lib/plugin/coverage.js +131 -78
- package/lib/plugin/customLocator.js +22 -21
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/heal.js +101 -110
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +12 -11
- package/lib/plugin/retryFailedStep.js +82 -47
- package/lib/plugin/screenshotOnFail.js +111 -92
- package/lib/plugin/stepByStepReport.js +159 -101
- package/lib/plugin/stepTimeout.js +20 -25
- package/lib/plugin/subtitles.js +38 -38
- package/lib/recorder.js +193 -130
- package/lib/rerun.js +94 -49
- package/lib/result.js +238 -0
- package/lib/retryCoordinator.js +207 -0
- package/lib/secret.js +20 -18
- package/lib/session.js +95 -89
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +18 -329
- package/lib/steps.js +54 -0
- package/lib/store.js +38 -7
- package/lib/template/heal.js +3 -12
- 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 +334 -0
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +34 -21
- package/lib/utils/loaderCheck.js +124 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils/typescript.js +237 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +124 -95
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +22 -12
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +10 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +546 -185
- package/typings/promiseBasedTypes.d.ts +150 -875
- package/typings/types.d.ts +547 -992
- package/lib/cli.js +0 -249
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/ExpectHelper.js +0 -399
- package/lib/helper/MockServer.js +0 -223
- package/lib/helper/Nightmare.js +0 -1411
- package/lib/helper/Protractor.js +0 -1835
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1410
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -63
- package/lib/interfaces/bdd.js +0 -98
- package/lib/interfaces/featureConfig.js +0 -69
- package/lib/interfaces/gherkin.js +0 -195
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/retry.js +0 -68
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -110
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -121
- package/lib/plugin/selenoid.js +0 -371
- package/lib/plugin/standardActingHelpers.js +0 -9
- package/lib/plugin/tryTo.js +0 -105
- package/lib/plugin/wdio.js +0 -246
- package/lib/scenario.js +0 -222
- package/lib/ui.js +0 -238
- package/lib/within.js +0 -70
package/lib/ai.js
CHANGED
|
@@ -1,218 +1,202 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
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))
|
|
7
12
|
|
|
8
13
|
const defaultHtmlConfig = {
|
|
9
14
|
maxLength: 50000,
|
|
10
15
|
simplify: true,
|
|
11
16
|
minify: true,
|
|
12
17
|
html: {},
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const { I } = inject();
|
|
43
|
-
|
|
44
|
-
module.exports = {
|
|
45
|
-
|
|
46
|
-
// setting locators
|
|
47
|
-
element1: '#selector',
|
|
48
|
-
element2: '.selector',
|
|
49
|
-
element3: locate().withText('text'),
|
|
50
|
-
|
|
51
|
-
// seting methods
|
|
52
|
-
doSomethingOnPage(params) {
|
|
53
|
-
// ...
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
I want to generate a Page Object for the page I provide.
|
|
58
|
-
Write JavaScript code in similar manner to list all locators on the page.
|
|
59
|
-
Use locators in order of preference: by text (use locate().withText()), label, CSS, XPath.
|
|
60
|
-
Avoid TailwindCSS, Bootstrap or React style formatting classes in locators.
|
|
61
|
-
Add methods to to interact with page when needed.
|
|
62
|
-
${extraPrompt}
|
|
63
|
-
${rootLocator ? `All provided elements are inside '${rootLocator}'. Declare it as root variable and for every locator use locate(...).inside(root)` : ''}
|
|
64
|
-
Add only locators from this HTML: \n\n${html}`,
|
|
65
|
-
}],
|
|
66
|
-
};
|
|
18
|
+
}
|
|
19
|
+
|
|
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
|
|
45
|
+
}
|
|
67
46
|
|
|
68
47
|
class AiAssistant {
|
|
69
48
|
constructor() {
|
|
70
|
-
this.totalTime = 0
|
|
71
|
-
this.numTokens = 0
|
|
49
|
+
this.totalTime = 0
|
|
50
|
+
this.numTokens = 0
|
|
72
51
|
|
|
73
|
-
this.reset()
|
|
74
|
-
this.connectToEvents()
|
|
52
|
+
this.reset()
|
|
53
|
+
this.connectToEvents()
|
|
75
54
|
}
|
|
76
55
|
|
|
77
|
-
enable(config = {}) {
|
|
78
|
-
debug('Enabling AI assistant')
|
|
79
|
-
this.isEnabled = true
|
|
56
|
+
async enable(config = {}) {
|
|
57
|
+
debug('Enabling AI assistant')
|
|
58
|
+
this.isEnabled = true
|
|
80
59
|
|
|
81
|
-
const { html, prompts, ...aiConfig } = config
|
|
60
|
+
const { html, prompts, ...aiConfig } = config
|
|
82
61
|
|
|
83
|
-
this.config = Object.assign(this.config, aiConfig)
|
|
84
|
-
this.htmlConfig = Object.assign(defaultHtmlConfig, html)
|
|
85
|
-
this.prompts = Object.assign(defaultPrompts, prompts);
|
|
62
|
+
this.config = Object.assign(this.config, aiConfig)
|
|
63
|
+
this.htmlConfig = Object.assign(defaultHtmlConfig, html)
|
|
86
64
|
|
|
87
|
-
|
|
65
|
+
const loadedPrompts = await loadPrompts()
|
|
66
|
+
this.prompts = Object.assign(loadedPrompts, prompts || {})
|
|
67
|
+
|
|
68
|
+
debug('Config', this.config)
|
|
88
69
|
}
|
|
89
70
|
|
|
90
71
|
reset() {
|
|
91
|
-
this.numTokens = 0
|
|
92
|
-
this.isEnabled = false
|
|
72
|
+
this.numTokens = 0
|
|
73
|
+
this.isEnabled = false
|
|
93
74
|
this.config = {
|
|
94
75
|
maxTokens: 1000000,
|
|
95
|
-
|
|
76
|
+
model: null,
|
|
96
77
|
response: parseCodeBlocks,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
this.
|
|
101
|
-
this.totalTime = 0;
|
|
78
|
+
}
|
|
79
|
+
this.minifiedHtml = null
|
|
80
|
+
this.response = null
|
|
81
|
+
this.totalTime = 0
|
|
102
82
|
}
|
|
103
83
|
|
|
104
84
|
disable() {
|
|
105
|
-
this.isEnabled = false
|
|
85
|
+
this.isEnabled = false
|
|
106
86
|
}
|
|
107
87
|
|
|
108
88
|
connectToEvents() {
|
|
109
89
|
event.dispatcher.on(event.all.result, () => {
|
|
110
90
|
if (this.isEnabled && this.numTokens > 0) {
|
|
111
|
-
const numTokensK = Math.ceil(this.numTokens / 1000)
|
|
112
|
-
const maxTokensK = Math.ceil(this.config.maxTokens / 1000)
|
|
113
|
-
output.print(`AI assistant took ${this.totalTime}s and used
|
|
91
|
+
const numTokensK = Math.ceil(this.numTokens / 1000)
|
|
92
|
+
const maxTokensK = Math.ceil(this.config.maxTokens / 1000)
|
|
93
|
+
output.print(`AI assistant took ${this.totalTime}s and used ${numTokensK}K tokens. Tokens limit: ${maxTokensK}K`)
|
|
114
94
|
}
|
|
115
|
-
})
|
|
95
|
+
})
|
|
116
96
|
}
|
|
117
97
|
|
|
118
|
-
|
|
98
|
+
checkModel() {
|
|
119
99
|
if (!this.isEnabled) {
|
|
120
|
-
debug('AI assistant is disabled')
|
|
121
|
-
return
|
|
100
|
+
debug('AI assistant is disabled')
|
|
101
|
+
return
|
|
122
102
|
}
|
|
123
103
|
|
|
124
|
-
if (this.config.
|
|
104
|
+
if (this.config.model) return
|
|
125
105
|
|
|
126
|
-
const
|
|
127
|
-
No
|
|
128
|
-
Please implement your own request function and set it in the config.
|
|
106
|
+
const noModelErrorMessage = `
|
|
107
|
+
No model is set for AI assistant.
|
|
129
108
|
|
|
130
|
-
[!]
|
|
109
|
+
[!] Please configure AI model using Vercel AI SDK providers.
|
|
131
110
|
|
|
132
111
|
Example (connect to OpenAI):
|
|
133
112
|
|
|
113
|
+
import { openai } from '@ai-sdk/openai';
|
|
114
|
+
|
|
134
115
|
ai: {
|
|
135
|
-
|
|
136
|
-
const OpenAI = require('openai');
|
|
137
|
-
const openai = new OpenAI({ apiKey: process.env['OPENAI_API_KEY'] })
|
|
138
|
-
const response = await openai.chat.completions.create({
|
|
139
|
-
model: 'gpt-3.5-turbo-0125',
|
|
140
|
-
messages,
|
|
141
|
-
});
|
|
142
|
-
return response?.data?.choices[0]?.message?.content;
|
|
143
|
-
}
|
|
116
|
+
model: openai('gpt-4o-mini')
|
|
144
117
|
}
|
|
145
|
-
`.trim();
|
|
146
118
|
|
|
147
|
-
|
|
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.
|
|
128
|
+
`.trim()
|
|
129
|
+
|
|
130
|
+
throw new Error(noModelErrorMessage)
|
|
148
131
|
}
|
|
149
132
|
|
|
150
133
|
async setHtmlContext(html) {
|
|
151
|
-
let processedHTML = html
|
|
134
|
+
let processedHTML = html
|
|
152
135
|
|
|
153
136
|
if (this.htmlConfig.simplify) {
|
|
154
|
-
processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig)
|
|
137
|
+
processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig)
|
|
155
138
|
}
|
|
156
139
|
|
|
157
|
-
if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML)
|
|
158
|
-
if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0]
|
|
140
|
+
if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML)
|
|
141
|
+
if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0]
|
|
159
142
|
|
|
160
|
-
this.minifiedHtml = processedHTML
|
|
143
|
+
this.minifiedHtml = processedHTML
|
|
161
144
|
}
|
|
162
145
|
|
|
163
146
|
getResponse() {
|
|
164
|
-
return this.response || ''
|
|
147
|
+
return this.response || ''
|
|
165
148
|
}
|
|
166
149
|
|
|
167
150
|
async createCompletion(messages) {
|
|
168
|
-
if (!this.isEnabled) return ''
|
|
169
|
-
|
|
170
|
-
debug('Request', messages);
|
|
171
|
-
|
|
172
|
-
this.checkRequestFn();
|
|
173
|
-
|
|
174
|
-
this.response = null;
|
|
175
|
-
|
|
176
|
-
this.calculateTokens(messages);
|
|
151
|
+
if (!this.isEnabled) return ''
|
|
177
152
|
|
|
178
153
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
154
|
+
this.checkModel()
|
|
155
|
+
debug('Request', messages)
|
|
156
|
+
|
|
157
|
+
this.response = null
|
|
158
|
+
|
|
159
|
+
const startTime = process.hrtime()
|
|
160
|
+
const result = await generateText({
|
|
161
|
+
model: this.config.model,
|
|
162
|
+
messages,
|
|
163
|
+
})
|
|
164
|
+
const endTime = process.hrtime(startTime)
|
|
165
|
+
const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9
|
|
166
|
+
|
|
167
|
+
this.response = result.text
|
|
168
|
+
this.numTokens += result.usage.totalTokens
|
|
169
|
+
|
|
170
|
+
this.totalTime += Math.round(executionTimeInSeconds)
|
|
171
|
+
debug('AI response time', executionTimeInSeconds)
|
|
172
|
+
debug('Response', this.response)
|
|
173
|
+
debug('Usage', result.usage)
|
|
174
|
+
this.stopWhenReachingTokensLimit()
|
|
175
|
+
return this.response
|
|
189
176
|
} catch (err) {
|
|
190
|
-
debug(err
|
|
191
|
-
output.print('')
|
|
192
|
-
output.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
this.stopWhenReachingTokensLimit();
|
|
196
|
-
|
|
197
|
-
return '';
|
|
177
|
+
debug(err)
|
|
178
|
+
output.print('')
|
|
179
|
+
output.error(`AI service error: ${err.message}`)
|
|
180
|
+
this.stopWhenReachingTokensLimit()
|
|
181
|
+
return ''
|
|
198
182
|
}
|
|
199
183
|
}
|
|
200
184
|
|
|
201
185
|
async healFailedStep(failureContext) {
|
|
202
|
-
if (!this.isEnabled) return []
|
|
203
|
-
if (!failureContext.html) throw new Error('No HTML context provided')
|
|
186
|
+
if (!this.isEnabled) return []
|
|
187
|
+
if (!failureContext.html) throw new Error('No HTML context provided')
|
|
204
188
|
|
|
205
|
-
await this.setHtmlContext(failureContext.html)
|
|
189
|
+
await this.setHtmlContext(failureContext.html)
|
|
206
190
|
|
|
207
191
|
if (!this.minifiedHtml) {
|
|
208
|
-
debug('HTML context is empty after removing non-interactive elements & minification')
|
|
209
|
-
return []
|
|
192
|
+
debug('HTML context is empty after removing non-interactive elements & minification')
|
|
193
|
+
return []
|
|
210
194
|
}
|
|
211
195
|
|
|
212
|
-
const response = await this.createCompletion(this.prompts.healStep(this.minifiedHtml, failureContext))
|
|
213
|
-
if (!response) return []
|
|
196
|
+
const response = await this.createCompletion(this.prompts.healStep(this.minifiedHtml, failureContext))
|
|
197
|
+
if (!response) return []
|
|
214
198
|
|
|
215
|
-
return this.config.response(response)
|
|
199
|
+
return this.config.response(response)
|
|
216
200
|
}
|
|
217
201
|
|
|
218
202
|
/**
|
|
@@ -222,80 +206,67 @@ class AiAssistant {
|
|
|
222
206
|
* @returns
|
|
223
207
|
*/
|
|
224
208
|
async generatePageObject(extraPrompt = null, locator = null) {
|
|
225
|
-
if (!this.isEnabled) return []
|
|
226
|
-
if (!this.minifiedHtml) throw new Error('No HTML context provided')
|
|
227
|
-
|
|
228
|
-
const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt));
|
|
229
|
-
if (!response) return [];
|
|
230
|
-
|
|
231
|
-
return this.config.response(response);
|
|
232
|
-
}
|
|
233
|
-
|
|
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.map(m => m.content).join(' ').trim();
|
|
240
|
-
const numWords = (inputString.match(/[^\s\-:=]+/g) || []).length;
|
|
241
|
-
|
|
242
|
-
// 2.5 token is constant for average HTML input
|
|
243
|
-
const tokens = numWords * 2.5;
|
|
209
|
+
if (!this.isEnabled) return []
|
|
210
|
+
if (!this.minifiedHtml) throw new Error('No HTML context provided')
|
|
244
211
|
|
|
245
|
-
this.
|
|
212
|
+
const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt))
|
|
213
|
+
if (!response) return []
|
|
246
214
|
|
|
247
|
-
return
|
|
215
|
+
return this.config.response(response)
|
|
248
216
|
}
|
|
249
217
|
|
|
250
218
|
stopWhenReachingTokensLimit() {
|
|
251
|
-
if (this.numTokens < this.config.maxTokens) return
|
|
219
|
+
if (this.numTokens < this.config.maxTokens) return
|
|
252
220
|
|
|
253
|
-
output.print(`AI assistant has reached the limit of ${this.config.maxTokens} tokens in this session. It will be disabled now`)
|
|
254
|
-
this.disable()
|
|
221
|
+
output.print(`AI assistant has reached the limit of ${this.config.maxTokens} tokens in this session. It will be disabled now`)
|
|
222
|
+
this.disable()
|
|
255
223
|
}
|
|
256
224
|
|
|
257
225
|
async writeSteps(input) {
|
|
258
|
-
if (!this.isEnabled) return
|
|
259
|
-
if (!this.minifiedHtml) throw new Error('No HTML context provided')
|
|
226
|
+
if (!this.isEnabled) return
|
|
227
|
+
if (!this.minifiedHtml) throw new Error('No HTML context provided')
|
|
260
228
|
|
|
261
|
-
const snippets = []
|
|
229
|
+
const snippets = []
|
|
262
230
|
|
|
263
|
-
const response = await this.createCompletion(this.prompts.writeStep(this.minifiedHtml, input))
|
|
264
|
-
if (!response) return
|
|
265
|
-
snippets.push(...this.config.response(response))
|
|
231
|
+
const response = await this.createCompletion(this.prompts.writeStep(this.minifiedHtml, input))
|
|
232
|
+
if (!response) return
|
|
233
|
+
snippets.push(...this.config.response(response))
|
|
266
234
|
|
|
267
|
-
debug(snippets[0])
|
|
235
|
+
debug(snippets[0])
|
|
268
236
|
|
|
269
|
-
return snippets[0]
|
|
237
|
+
return snippets[0]
|
|
270
238
|
}
|
|
271
239
|
}
|
|
272
240
|
|
|
273
241
|
function parseCodeBlocks(response) {
|
|
274
242
|
// Regular expression pattern to match code snippets
|
|
275
|
-
const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g
|
|
243
|
+
const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g
|
|
276
244
|
|
|
277
245
|
// Array to store extracted code snippets
|
|
278
|
-
const codeSnippets = []
|
|
246
|
+
const codeSnippets = []
|
|
279
247
|
|
|
280
|
-
response = response
|
|
248
|
+
response = response
|
|
249
|
+
.split('\n')
|
|
250
|
+
.map(line => line.trim())
|
|
251
|
+
.join('\n')
|
|
281
252
|
|
|
282
253
|
// Iterate over matches and extract code snippets
|
|
283
|
-
let match
|
|
254
|
+
let match
|
|
284
255
|
while ((match = codeSnippetPattern.exec(response)) !== null) {
|
|
285
|
-
codeSnippets.push(match[1])
|
|
256
|
+
codeSnippets.push(match[1])
|
|
286
257
|
}
|
|
287
258
|
|
|
288
259
|
// Remove "Scenario", "Feature", and "require()" lines
|
|
289
260
|
const modifiedSnippets = codeSnippets.map(snippet => {
|
|
290
|
-
const lines = snippet.split('\n')
|
|
261
|
+
const lines = snippet.split('\n')
|
|
291
262
|
|
|
292
|
-
const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('))
|
|
263
|
+
const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('))
|
|
293
264
|
|
|
294
|
-
return filteredLines.join('\n')
|
|
265
|
+
return filteredLines.join('\n')
|
|
295
266
|
// remove snippets that move from current url
|
|
296
|
-
})
|
|
267
|
+
}) // .filter(snippet => !line.includes('I.amOnPage'));
|
|
297
268
|
|
|
298
|
-
return modifiedSnippets.filter(snippet => !!snippet)
|
|
269
|
+
return modifiedSnippets.filter(snippet => !!snippet)
|
|
299
270
|
}
|
|
300
271
|
|
|
301
|
-
export default new AiAssistant()
|
|
272
|
+
export default new AiAssistant()
|
package/lib/assert/empty.js
CHANGED
|
@@ -1,44 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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) {
|
|
8
|
-
super(
|
|
10
|
+
super(value => {
|
|
9
11
|
if (Array.isArray(value)) {
|
|
10
|
-
return value.length === 0
|
|
12
|
+
return value.length === 0
|
|
11
13
|
}
|
|
12
|
-
return !value
|
|
13
|
-
}, params)
|
|
14
|
-
this.params.type = 'to be empty'
|
|
14
|
+
return !value
|
|
15
|
+
}, params)
|
|
16
|
+
this.params.type = 'to be empty'
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
getException() {
|
|
18
20
|
if (Array.isArray(this.params.value)) {
|
|
19
|
-
this.params.value = `[${this.params.value.join(', ')}]
|
|
21
|
+
this.params.value = `[${this.params.value.join(', ')}]`
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
const err = new AssertionFailedError(this.params, "{{customMessage}}expected {{subject}} '{{value}}' {{type}}")
|
|
24
|
+
const err = new AssertionFailedError(this.params, "{{customMessage}}expected {{subject}} '{{value}}' {{type}}")
|
|
23
25
|
|
|
24
26
|
err.cliMessage = () => {
|
|
25
|
-
const msg = err.template
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
30
|
-
return err;
|
|
27
|
+
const msg = err.template.replace('{{value}}', output.colors.bold('{{value}}')).replace('{{subject}}', output.colors.bold('{{subject}}'))
|
|
28
|
+
return template(msg, this.params)
|
|
29
|
+
}
|
|
30
|
+
return err
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
addAssertParams() {
|
|
34
|
-
this.params.value = this.params.actual = arguments[0]
|
|
35
|
-
this.params.expected = []
|
|
36
|
-
this.params.customMessage = arguments[1] ? `${arguments[1]}\n\n` : ''
|
|
34
|
+
this.params.value = this.params.actual = arguments[0]
|
|
35
|
+
this.params.expected = []
|
|
36
|
+
this.params.customMessage = arguments[1] ? `${arguments[1]}\n\n` : ''
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export { EmptinessAssertion as Assertion }
|
|
40
|
+
export { EmptinessAssertion as Assertion }
|
|
41
41
|
|
|
42
|
-
export
|
|
43
|
-
return new EmptinessAssertion({ subject });
|
|
44
|
-
}
|
|
42
|
+
export const empty = subject => new EmptinessAssertion({ subject })
|
package/lib/assert/equal.js
CHANGED
|
@@ -1,59 +1,52 @@
|
|
|
1
|
-
import Assertion from '../assert.js'
|
|
2
|
-
import AssertionFailedError from './error.js'
|
|
3
|
-
import { template } from '../utils.js'
|
|
4
|
-
import
|
|
1
|
+
import Assertion from '../assert.js'
|
|
2
|
+
import AssertionFailedError from './error.js'
|
|
3
|
+
import { template } from '../utils.js'
|
|
4
|
+
import output from '../output.js'
|
|
5
5
|
|
|
6
6
|
class EqualityAssertion extends Assertion {
|
|
7
7
|
constructor(params) {
|
|
8
8
|
const comparator = function (a, b) {
|
|
9
9
|
if (b.length === 0) {
|
|
10
|
-
b = ''
|
|
10
|
+
b = ''
|
|
11
11
|
}
|
|
12
|
-
return a === b
|
|
13
|
-
}
|
|
14
|
-
super(comparator, params)
|
|
15
|
-
this.params.type = 'to equal'
|
|
12
|
+
return a === b
|
|
13
|
+
}
|
|
14
|
+
super(comparator, params)
|
|
15
|
+
this.params.type = 'to equal'
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
getException() {
|
|
19
|
-
const params = this.params
|
|
20
|
-
params.jar = template(params.jar, params)
|
|
21
|
-
const err = new AssertionFailedError(params, '{{customMessage}}expected {{jar}} "{{expected}}" {{type}} "{{actual}}"')
|
|
22
|
-
err.showDiff = false
|
|
19
|
+
const params = this.params
|
|
20
|
+
params.jar = template(params.jar, params)
|
|
21
|
+
const err = new AssertionFailedError(params, '{{customMessage}}expected {{jar}} "{{expected}}" {{type}} "{{actual}}"')
|
|
22
|
+
err.showDiff = false
|
|
23
23
|
if (typeof err.cliMessage === 'function') {
|
|
24
|
-
err.message = err.cliMessage()
|
|
24
|
+
err.message = err.cliMessage()
|
|
25
25
|
}
|
|
26
26
|
err.cliMessage = () => {
|
|
27
|
-
const msg = err.template
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return err;
|
|
27
|
+
const msg = err.template.replace('{{jar}}', output.colors.bold('{{jar}}'))
|
|
28
|
+
return template(msg, this.params)
|
|
29
|
+
}
|
|
30
|
+
return err
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
addAssertParams() {
|
|
35
|
-
this.params.expected = arguments[0]
|
|
36
|
-
this.params.actual = arguments[1]
|
|
37
|
-
this.params.customMessage = arguments[2] ? `${arguments[2]}\n\n` : ''
|
|
34
|
+
this.params.expected = arguments[0]
|
|
35
|
+
this.params.actual = arguments[1]
|
|
36
|
+
this.params.customMessage = arguments[2] ? `${arguments[2]}\n\n` : ''
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export { EqualityAssertion as Assertion };
|
|
46
|
-
export function equals(jar) {
|
|
47
|
-
return new EqualityAssertion({ jar });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function urlEquals(baseUrl) {
|
|
51
|
-
const assert = new EqualityAssertion({ jar: 'url of current page' });
|
|
40
|
+
export { EqualityAssertion as Assertion }
|
|
41
|
+
export const equals = jar => new EqualityAssertion({ jar })
|
|
42
|
+
export const urlEquals = baseUrl => {
|
|
43
|
+
const assert = new EqualityAssertion({ jar: 'url of current page' })
|
|
52
44
|
assert.comparator = function (expected, actual) {
|
|
53
45
|
if (expected.indexOf('http') !== 0) {
|
|
54
|
-
actual = actual.slice(actual.indexOf(baseUrl) + baseUrl.length)
|
|
46
|
+
actual = actual.slice(actual.indexOf(baseUrl) + baseUrl.length)
|
|
55
47
|
}
|
|
56
|
-
return actual === expected
|
|
57
|
-
}
|
|
58
|
-
return assert
|
|
48
|
+
return actual === expected
|
|
49
|
+
}
|
|
50
|
+
return assert
|
|
59
51
|
}
|
|
52
|
+
export const fileEquals = file => new EqualityAssertion({ file, jar: 'contents of {{file}}' })
|
package/lib/assert/error.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { template as subs } from '../utils.js'
|
|
1
|
+
import { template as subs } from '../utils.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Assertion errors, can provide a detailed error messages.
|
|
@@ -6,27 +6,27 @@ import { template as subs } from '../utils.js';
|
|
|
6
6
|
* inspect() and cliMessage() added to display errors with params.
|
|
7
7
|
*/
|
|
8
8
|
function AssertionFailedError(params, template) {
|
|
9
|
-
this.params = params
|
|
10
|
-
this.template = template
|
|
9
|
+
this.params = params
|
|
10
|
+
this.template = template
|
|
11
11
|
// this.message = "AssertionFailedError";
|
|
12
12
|
// this.showDiff = true;
|
|
13
13
|
|
|
14
14
|
// @todo cut assert things nicer
|
|
15
|
-
this.showDiff = true
|
|
15
|
+
this.showDiff = true
|
|
16
16
|
|
|
17
|
-
this.actual = this.params.actual
|
|
18
|
-
this.expected = this.params.expected
|
|
17
|
+
this.actual = this.params.actual
|
|
18
|
+
this.expected = this.params.expected
|
|
19
19
|
|
|
20
20
|
this.inspect = () => {
|
|
21
|
-
const params = this.params || {}
|
|
22
|
-
const msg = params.customMessage || ''
|
|
23
|
-
return msg + subs(this.template, params)
|
|
24
|
-
}
|
|
21
|
+
const params = this.params || {}
|
|
22
|
+
const msg = params.customMessage || ''
|
|
23
|
+
return msg + subs(this.template, params)
|
|
24
|
+
}
|
|
25
25
|
|
|
26
|
-
this.cliMessage = () => this.inspect()
|
|
26
|
+
this.cliMessage = () => this.inspect()
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
AssertionFailedError.prototype = Object.create(Error.prototype)
|
|
30
|
-
AssertionFailedError.constructor = AssertionFailedError
|
|
29
|
+
AssertionFailedError.prototype = Object.create(Error.prototype)
|
|
30
|
+
AssertionFailedError.constructor = AssertionFailedError
|
|
31
31
|
|
|
32
|
-
export default AssertionFailedError
|
|
32
|
+
export default AssertionFailedError
|