codeceptjs 4.0.0-beta.1 → 4.0.0-beta.11.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 (207) hide show
  1. package/README.md +133 -120
  2. package/bin/codecept.js +107 -96
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/docs/webapi/click.mustache +5 -1
  6. package/lib/actor.js +71 -103
  7. package/lib/ai.js +159 -188
  8. package/lib/assert/empty.js +22 -24
  9. package/lib/assert/equal.js +30 -37
  10. package/lib/assert/error.js +14 -14
  11. package/lib/assert/include.js +43 -48
  12. package/lib/assert/throws.js +11 -11
  13. package/lib/assert/truth.js +22 -22
  14. package/lib/assert.js +20 -18
  15. package/lib/codecept.js +238 -162
  16. package/lib/colorUtils.js +50 -52
  17. package/lib/command/check.js +206 -0
  18. package/lib/command/configMigrate.js +56 -51
  19. package/lib/command/definitions.js +96 -109
  20. package/lib/command/dryRun.js +77 -79
  21. package/lib/command/generate.js +234 -194
  22. package/lib/command/gherkin/init.js +42 -33
  23. package/lib/command/gherkin/snippets.js +76 -74
  24. package/lib/command/gherkin/steps.js +20 -17
  25. package/lib/command/info.js +74 -38
  26. package/lib/command/init.js +300 -290
  27. package/lib/command/interactive.js +41 -32
  28. package/lib/command/list.js +28 -27
  29. package/lib/command/run-multiple/chunk.js +51 -48
  30. package/lib/command/run-multiple/collection.js +5 -5
  31. package/lib/command/run-multiple/run.js +5 -1
  32. package/lib/command/run-multiple.js +97 -97
  33. package/lib/command/run-rerun.js +19 -25
  34. package/lib/command/run-workers.js +68 -92
  35. package/lib/command/run.js +39 -27
  36. package/lib/command/utils.js +80 -64
  37. package/lib/command/workers/runTests.js +388 -226
  38. package/lib/config.js +124 -50
  39. package/lib/container.js +765 -260
  40. package/lib/data/context.js +60 -61
  41. package/lib/data/dataScenarioConfig.js +47 -47
  42. package/lib/data/dataTableArgument.js +32 -32
  43. package/lib/data/table.js +22 -22
  44. package/lib/effects.js +307 -0
  45. package/lib/element/WebElement.js +327 -0
  46. package/lib/els.js +160 -0
  47. package/lib/event.js +173 -163
  48. package/lib/globals.js +141 -0
  49. package/lib/heal.js +89 -85
  50. package/lib/helper/AI.js +131 -41
  51. package/lib/helper/ApiDataFactory.js +107 -75
  52. package/lib/helper/Appium.js +542 -404
  53. package/lib/helper/FileSystem.js +100 -79
  54. package/lib/helper/GraphQL.js +44 -43
  55. package/lib/helper/GraphQLDataFactory.js +52 -52
  56. package/lib/helper/JSONResponse.js +126 -88
  57. package/lib/helper/Mochawesome.js +54 -29
  58. package/lib/helper/Playwright.js +2547 -1316
  59. package/lib/helper/Puppeteer.js +1578 -1181
  60. package/lib/helper/REST.js +209 -68
  61. package/lib/helper/WebDriver.js +1482 -1342
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
  71. package/lib/helper/extras/Popup.js +22 -22
  72. package/lib/helper/extras/React.js +27 -28
  73. package/lib/helper/network/actions.js +36 -42
  74. package/lib/helper/network/utils.js +78 -84
  75. package/lib/helper/scripts/blurElement.js +5 -5
  76. package/lib/helper/scripts/focusElement.js +5 -5
  77. package/lib/helper/scripts/highlightElement.js +8 -8
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -3
  80. package/lib/history.js +23 -19
  81. package/lib/hooks.js +8 -8
  82. package/lib/html.js +94 -104
  83. package/lib/index.js +38 -27
  84. package/lib/listener/config.js +30 -23
  85. package/lib/listener/emptyRun.js +54 -0
  86. package/lib/listener/enhancedGlobalRetry.js +110 -0
  87. package/lib/listener/exit.js +16 -18
  88. package/lib/listener/globalRetry.js +70 -0
  89. package/lib/listener/globalTimeout.js +181 -0
  90. package/lib/listener/helpers.js +76 -51
  91. package/lib/listener/mocha.js +10 -11
  92. package/lib/listener/result.js +11 -0
  93. package/lib/listener/retryEnhancer.js +85 -0
  94. package/lib/listener/steps.js +71 -59
  95. package/lib/listener/store.js +20 -0
  96. package/lib/locator.js +214 -197
  97. package/lib/mocha/asyncWrapper.js +274 -0
  98. package/lib/mocha/bdd.js +167 -0
  99. package/lib/mocha/cli.js +341 -0
  100. package/lib/mocha/factory.js +163 -0
  101. package/lib/mocha/featureConfig.js +89 -0
  102. package/lib/mocha/gherkin.js +231 -0
  103. package/lib/mocha/hooks.js +121 -0
  104. package/lib/mocha/index.js +21 -0
  105. package/lib/mocha/inject.js +46 -0
  106. package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
  107. package/lib/mocha/suite.js +89 -0
  108. package/lib/mocha/test.js +184 -0
  109. package/lib/mocha/types.d.ts +42 -0
  110. package/lib/mocha/ui.js +242 -0
  111. package/lib/output.js +141 -71
  112. package/lib/parser.js +47 -44
  113. package/lib/pause.js +173 -145
  114. package/lib/plugin/analyze.js +403 -0
  115. package/lib/plugin/{autoLogin.js → auth.js} +178 -79
  116. package/lib/plugin/autoDelay.js +36 -40
  117. package/lib/plugin/coverage.js +131 -78
  118. package/lib/plugin/customLocator.js +22 -21
  119. package/lib/plugin/customReporter.js +53 -0
  120. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  121. package/lib/plugin/heal.js +101 -110
  122. package/lib/plugin/htmlReporter.js +3648 -0
  123. package/lib/plugin/pageInfo.js +140 -0
  124. package/lib/plugin/pauseOnFail.js +12 -11
  125. package/lib/plugin/retryFailedStep.js +82 -47
  126. package/lib/plugin/screenshotOnFail.js +111 -92
  127. package/lib/plugin/stepByStepReport.js +159 -101
  128. package/lib/plugin/stepTimeout.js +20 -25
  129. package/lib/plugin/subtitles.js +38 -38
  130. package/lib/recorder.js +193 -130
  131. package/lib/rerun.js +94 -49
  132. package/lib/result.js +238 -0
  133. package/lib/retryCoordinator.js +207 -0
  134. package/lib/secret.js +20 -18
  135. package/lib/session.js +95 -89
  136. package/lib/step/base.js +239 -0
  137. package/lib/step/comment.js +10 -0
  138. package/lib/step/config.js +50 -0
  139. package/lib/step/func.js +46 -0
  140. package/lib/step/helper.js +50 -0
  141. package/lib/step/meta.js +99 -0
  142. package/lib/step/record.js +74 -0
  143. package/lib/step/retry.js +11 -0
  144. package/lib/step/section.js +55 -0
  145. package/lib/step.js +18 -329
  146. package/lib/steps.js +54 -0
  147. package/lib/store.js +38 -7
  148. package/lib/template/heal.js +3 -12
  149. package/lib/template/prompts/generatePageObject.js +31 -0
  150. package/lib/template/prompts/healStep.js +13 -0
  151. package/lib/template/prompts/writeStep.js +9 -0
  152. package/lib/test-server.js +334 -0
  153. package/lib/timeout.js +60 -0
  154. package/lib/transform.js +8 -8
  155. package/lib/translation.js +34 -21
  156. package/lib/utils/mask_data.js +47 -0
  157. package/lib/utils.js +411 -228
  158. package/lib/workerStorage.js +37 -34
  159. package/lib/workers.js +532 -296
  160. package/package.json +115 -95
  161. package/translations/de-DE.js +5 -3
  162. package/translations/fr-FR.js +5 -4
  163. package/translations/index.js +22 -12
  164. package/translations/it-IT.js +4 -3
  165. package/translations/ja-JP.js +4 -3
  166. package/translations/nl-NL.js +76 -0
  167. package/translations/pl-PL.js +4 -3
  168. package/translations/pt-BR.js +4 -3
  169. package/translations/ru-RU.js +4 -3
  170. package/translations/utils.js +10 -0
  171. package/translations/zh-CN.js +4 -3
  172. package/translations/zh-TW.js +4 -3
  173. package/typings/index.d.ts +546 -185
  174. package/typings/promiseBasedTypes.d.ts +150 -879
  175. package/typings/types.d.ts +547 -996
  176. package/lib/cli.js +0 -249
  177. package/lib/dirname.js +0 -5
  178. package/lib/helper/Expect.js +0 -425
  179. package/lib/helper/ExpectHelper.js +0 -399
  180. package/lib/helper/MockServer.js +0 -223
  181. package/lib/helper/Nightmare.js +0 -1411
  182. package/lib/helper/Protractor.js +0 -1835
  183. package/lib/helper/SoftExpectHelper.js +0 -381
  184. package/lib/helper/TestCafe.js +0 -1410
  185. package/lib/helper/clientscripts/nightmare.js +0 -213
  186. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  187. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  188. package/lib/interfaces/bdd.js +0 -98
  189. package/lib/interfaces/featureConfig.js +0 -69
  190. package/lib/interfaces/gherkin.js +0 -195
  191. package/lib/listener/artifacts.js +0 -19
  192. package/lib/listener/retry.js +0 -68
  193. package/lib/listener/timeout.js +0 -109
  194. package/lib/mochaFactory.js +0 -110
  195. package/lib/plugin/allure.js +0 -15
  196. package/lib/plugin/commentStep.js +0 -136
  197. package/lib/plugin/debugErrors.js +0 -67
  198. package/lib/plugin/eachElement.js +0 -127
  199. package/lib/plugin/fakerTransform.js +0 -49
  200. package/lib/plugin/retryTo.js +0 -121
  201. package/lib/plugin/selenoid.js +0 -371
  202. package/lib/plugin/standardActingHelpers.js +0 -9
  203. package/lib/plugin/tryTo.js +0 -105
  204. package/lib/plugin/wdio.js +0 -246
  205. package/lib/scenario.js +0 -222
  206. package/lib/ui.js +0 -238
  207. package/lib/within.js +0 -70
package/lib/ai.js CHANGED
@@ -1,218 +1,202 @@
1
- import debug from 'debug';
2
- import * as event from './event.js';
3
- import * as output from './output.js';
4
- import { removeNonInteractiveElements, minifyHtml, splitByChunks } from './html.js';
5
-
6
- debug('codeceptjs:ai');
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
- const defaultPrompts = {
16
- writeStep: (html, input) => [{
17
- role: 'user',
18
- content: `I am test engineer writing test in CodeceptJS
19
- I have opened web page and I want to use CodeceptJS to ${input} on this page
20
- Provide me valid CodeceptJS code to accomplish it
21
- Use only locators from this HTML: \n\n${html}`,
22
- },
23
- ],
24
-
25
- healStep: (html, { step, error, prevSteps }) => {
26
- return [{
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
- generatePageObject: (html, extraPrompt = '', rootLocator = null) => [{
38
- role: 'user',
39
- content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
40
- Here is an sample page object:
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
- debug('Config', this.config);
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
- request: null,
76
+ model: null,
96
77
  response: parseCodeBlocks,
97
- // lets limit token usage to 1M
98
- };
99
- this.minifiedHtml = null;
100
- this.response = null;
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 ~${numTokensK}K input tokens. Tokens limit: ${maxTokensK}K`);
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
- checkRequestFn() {
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.request) return;
104
+ if (this.config.model) return
125
105
 
126
- const noRequestErrorMessage = `
127
- No request function is set for AI assistant.
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
- [!] AI request was decoupled from CodeceptJS. To connect to OpenAI or other AI service, please implement your own request function and set it in the config.
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
- request: async (messages) => {
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
- throw new Error(noRequestErrorMessage);
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
- const startTime = process.hrtime();
180
- this.response = await this.config.request(messages);
181
- const endTime = process.hrtime(startTime);
182
- const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9;
183
-
184
- this.totalTime += Math.round(executionTimeInSeconds);
185
- debug('AI response time', executionTimeInSeconds);
186
- debug('Response', this.response);
187
- this.stopWhenReachingTokensLimit();
188
- return this.response;
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.response);
191
- output.print('');
192
- output.output.error(`AI service error: ${err.message}`);
193
- if (err?.response?.data?.error?.code) output.output.error(err?.response?.data?.error?.code);
194
- if (err?.response?.data?.error?.message) output.output.error(err?.response?.data?.error?.message);
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.numTokens += tokens;
212
+ const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt))
213
+ if (!response) return []
246
214
 
247
- return tokens;
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.split('\n').map(line => line.trim()).join('\n');
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
- }); // .filter(snippet => !line.includes('I.amOnPage'));
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()
@@ -1,44 +1,42 @@
1
- import Assertion from '../assert.js';
2
- import AssertionFailedError from './error.js';
3
- import { template } from '../utils.js';
4
- import * as output from '../output.js';
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((value) => {
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
- .replace('{{value}}', output.colors.bold('{{value}}'))
27
- .replace('{{subject}}', output.colors.bold('{{subject}}'));
28
- return template(msg, this.params);
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 function empty(subject) {
43
- return new EmptinessAssertion({ subject });
44
- }
42
+ export const empty = subject => new EmptinessAssertion({ subject })
@@ -1,59 +1,52 @@
1
- import Assertion from '../assert.js';
2
- import AssertionFailedError from './error.js';
3
- import { template } from '../utils.js';
4
- import * as output from '../output.js';
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
- .replace('{{jar}}', output.colors.bold('{{jar}}'));
29
- return template(msg, this.params);
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 function fileEquals(file) {
42
- return new EqualityAssertion({ file, jar: 'contents of {{file}}' });
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}}' })
@@ -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