codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.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 (188) hide show
  1. package/README.md +89 -119
  2. package/bin/codecept.js +53 -54
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +70 -102
  5. package/lib/ai.js +131 -121
  6. package/lib/assert/empty.js +11 -12
  7. package/lib/assert/equal.js +16 -21
  8. package/lib/assert/error.js +2 -2
  9. package/lib/assert/include.js +11 -15
  10. package/lib/assert/throws.js +3 -5
  11. package/lib/assert/truth.js +10 -7
  12. package/lib/assert.js +18 -18
  13. package/lib/codecept.js +112 -101
  14. package/lib/colorUtils.js +48 -50
  15. package/lib/command/check.js +206 -0
  16. package/lib/command/configMigrate.js +13 -14
  17. package/lib/command/definitions.js +24 -36
  18. package/lib/command/dryRun.js +16 -16
  19. package/lib/command/generate.js +38 -39
  20. package/lib/command/gherkin/init.js +36 -38
  21. package/lib/command/gherkin/snippets.js +76 -74
  22. package/lib/command/gherkin/steps.js +21 -18
  23. package/lib/command/info.js +49 -15
  24. package/lib/command/init.js +41 -37
  25. package/lib/command/interactive.js +22 -13
  26. package/lib/command/list.js +11 -10
  27. package/lib/command/run-multiple/chunk.js +50 -47
  28. package/lib/command/run-multiple/collection.js +5 -5
  29. package/lib/command/run-multiple/run.js +3 -3
  30. package/lib/command/run-multiple.js +27 -47
  31. package/lib/command/run-rerun.js +6 -7
  32. package/lib/command/run-workers.js +15 -66
  33. package/lib/command/run.js +8 -8
  34. package/lib/command/utils.js +22 -21
  35. package/lib/command/workers/runTests.js +131 -241
  36. package/lib/config.js +111 -49
  37. package/lib/container.js +589 -244
  38. package/lib/data/context.js +16 -18
  39. package/lib/data/dataScenarioConfig.js +9 -9
  40. package/lib/data/dataTableArgument.js +7 -7
  41. package/lib/data/table.js +6 -12
  42. package/lib/effects.js +307 -0
  43. package/lib/els.js +160 -0
  44. package/lib/event.js +24 -19
  45. package/lib/globals.js +141 -0
  46. package/lib/heal.js +89 -81
  47. package/lib/helper/AI.js +3 -2
  48. package/lib/helper/ApiDataFactory.js +19 -19
  49. package/lib/helper/Appium.js +47 -51
  50. package/lib/helper/FileSystem.js +35 -15
  51. package/lib/helper/GraphQL.js +1 -1
  52. package/lib/helper/GraphQLDataFactory.js +4 -4
  53. package/lib/helper/JSONResponse.js +72 -45
  54. package/lib/helper/Mochawesome.js +14 -11
  55. package/lib/helper/Playwright.js +832 -434
  56. package/lib/helper/Puppeteer.js +393 -292
  57. package/lib/helper/REST.js +32 -27
  58. package/lib/helper/WebDriver.js +320 -219
  59. package/lib/helper/errors/ConnectionRefused.js +6 -6
  60. package/lib/helper/errors/ElementAssertion.js +11 -16
  61. package/lib/helper/errors/ElementNotFound.js +5 -9
  62. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  63. package/lib/helper/extras/Console.js +11 -11
  64. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  65. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  66. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  67. package/lib/helper/extras/Popup.js +22 -22
  68. package/lib/helper/extras/React.js +29 -30
  69. package/lib/helper/network/actions.js +33 -48
  70. package/lib/helper/network/utils.js +76 -83
  71. package/lib/helper/scripts/blurElement.js +6 -6
  72. package/lib/helper/scripts/focusElement.js +6 -6
  73. package/lib/helper/scripts/highlightElement.js +9 -9
  74. package/lib/helper/scripts/isElementClickable.js +34 -34
  75. package/lib/helper.js +2 -1
  76. package/lib/history.js +23 -20
  77. package/lib/hooks.js +10 -10
  78. package/lib/html.js +90 -100
  79. package/lib/index.js +48 -21
  80. package/lib/listener/config.js +8 -9
  81. package/lib/listener/emptyRun.js +54 -0
  82. package/lib/listener/exit.js +10 -12
  83. package/lib/listener/{retry.js → globalRetry.js} +10 -10
  84. package/lib/listener/globalTimeout.js +166 -0
  85. package/lib/listener/helpers.js +43 -24
  86. package/lib/listener/mocha.js +4 -5
  87. package/lib/listener/result.js +11 -0
  88. package/lib/listener/steps.js +26 -23
  89. package/lib/listener/store.js +20 -0
  90. package/lib/locator.js +213 -192
  91. package/lib/mocha/asyncWrapper.js +264 -0
  92. package/lib/mocha/bdd.js +167 -0
  93. package/lib/mocha/cli.js +341 -0
  94. package/lib/mocha/factory.js +160 -0
  95. package/lib/{interfaces → mocha}/featureConfig.js +33 -13
  96. package/lib/{interfaces → mocha}/gherkin.js +75 -45
  97. package/lib/mocha/hooks.js +121 -0
  98. package/lib/mocha/index.js +21 -0
  99. package/lib/mocha/inject.js +46 -0
  100. package/lib/{interfaces → mocha}/scenarioConfig.js +32 -8
  101. package/lib/mocha/suite.js +89 -0
  102. package/lib/mocha/test.js +178 -0
  103. package/lib/mocha/types.d.ts +42 -0
  104. package/lib/mocha/ui.js +229 -0
  105. package/lib/output.js +86 -64
  106. package/lib/parser.js +44 -44
  107. package/lib/pause.js +160 -139
  108. package/lib/plugin/analyze.js +403 -0
  109. package/lib/plugin/{autoLogin.js → auth.js} +137 -43
  110. package/lib/plugin/autoDelay.js +19 -15
  111. package/lib/plugin/coverage.js +22 -27
  112. package/lib/plugin/customLocator.js +5 -5
  113. package/lib/plugin/customReporter.js +53 -0
  114. package/lib/plugin/heal.js +49 -17
  115. package/lib/plugin/pageInfo.js +140 -0
  116. package/lib/plugin/pauseOnFail.js +4 -3
  117. package/lib/plugin/retryFailedStep.js +60 -19
  118. package/lib/plugin/screenshotOnFail.js +80 -83
  119. package/lib/plugin/stepByStepReport.js +70 -31
  120. package/lib/plugin/stepTimeout.js +7 -13
  121. package/lib/plugin/subtitles.js +10 -9
  122. package/lib/recorder.js +167 -126
  123. package/lib/rerun.js +94 -50
  124. package/lib/result.js +161 -0
  125. package/lib/secret.js +18 -17
  126. package/lib/session.js +95 -89
  127. package/lib/step/base.js +239 -0
  128. package/lib/step/comment.js +10 -0
  129. package/lib/step/config.js +50 -0
  130. package/lib/step/func.js +46 -0
  131. package/lib/step/helper.js +50 -0
  132. package/lib/step/meta.js +99 -0
  133. package/lib/step/record.js +74 -0
  134. package/lib/step/retry.js +11 -0
  135. package/lib/step/section.js +55 -0
  136. package/lib/step.js +18 -332
  137. package/lib/steps.js +54 -0
  138. package/lib/store.js +37 -5
  139. package/lib/template/heal.js +2 -11
  140. package/lib/timeout.js +60 -0
  141. package/lib/transform.js +8 -8
  142. package/lib/translation.js +32 -18
  143. package/lib/utils.js +354 -250
  144. package/lib/workerStorage.js +16 -16
  145. package/lib/workers.js +366 -282
  146. package/package.json +107 -95
  147. package/translations/de-DE.js +5 -4
  148. package/translations/fr-FR.js +5 -4
  149. package/translations/index.js +23 -9
  150. package/translations/it-IT.js +5 -4
  151. package/translations/ja-JP.js +5 -4
  152. package/translations/nl-NL.js +76 -0
  153. package/translations/pl-PL.js +5 -4
  154. package/translations/pt-BR.js +5 -4
  155. package/translations/ru-RU.js +5 -4
  156. package/translations/utils.js +18 -0
  157. package/translations/zh-CN.js +5 -4
  158. package/translations/zh-TW.js +5 -4
  159. package/typings/index.d.ts +177 -186
  160. package/typings/promiseBasedTypes.d.ts +3573 -5941
  161. package/typings/types.d.ts +4042 -6370
  162. package/lib/cli.js +0 -256
  163. package/lib/helper/ExpectHelper.js +0 -391
  164. package/lib/helper/Nightmare.js +0 -1504
  165. package/lib/helper/Protractor.js +0 -1863
  166. package/lib/helper/SoftExpectHelper.js +0 -381
  167. package/lib/helper/TestCafe.js +0 -1414
  168. package/lib/helper/clientscripts/nightmare.js +0 -213
  169. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  170. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  171. package/lib/helper/testcafe/testcafe-utils.js +0 -62
  172. package/lib/interfaces/bdd.js +0 -81
  173. package/lib/listener/artifacts.js +0 -19
  174. package/lib/listener/timeout.js +0 -109
  175. package/lib/mochaFactory.js +0 -113
  176. package/lib/plugin/allure.js +0 -15
  177. package/lib/plugin/commentStep.js +0 -136
  178. package/lib/plugin/debugErrors.js +0 -67
  179. package/lib/plugin/eachElement.js +0 -127
  180. package/lib/plugin/fakerTransform.js +0 -49
  181. package/lib/plugin/retryTo.js +0 -127
  182. package/lib/plugin/selenoid.js +0 -384
  183. package/lib/plugin/standardActingHelpers.js +0 -3
  184. package/lib/plugin/tryTo.js +0 -115
  185. package/lib/plugin/wdio.js +0 -249
  186. package/lib/scenario.js +0 -224
  187. package/lib/ui.js +0 -236
  188. package/lib/within.js +0 -70
package/lib/actor.js CHANGED
@@ -1,11 +1,12 @@
1
- const Step = require('./step');
2
- const { MetaStep } = require('./step');
3
- const container = require('./container');
4
- const { methodsOfObject } = require('./utils');
5
- const recorder = require('./recorder');
6
- const event = require('./event');
7
- const store = require('./store');
8
- const output = require('./output');
1
+ import Step, { MetaStep } from './step.js'
2
+ import recordStep from './step/record.js'
3
+ import retryStep from './step/retry.js'
4
+ import { methodsOfObject } from './utils.js'
5
+ import { TIMEOUT_ORDER } from './timeout.js'
6
+ import event from './event.js'
7
+ import store from './store.js'
8
+ import output from './output.js'
9
+ import Container from './container.js'
9
10
 
10
11
  /**
11
12
  * @interface
@@ -21,13 +22,13 @@ class Actor {
21
22
  * ⚠️ returns a promise which is synchronized internally by recorder
22
23
  */
23
24
  async say(msg, color = 'cyan') {
24
- const step = new Step('say', 'say');
25
- step.status = 'passed';
25
+ const step = new Step('say', 'say')
26
+ step.status = 'passed'
26
27
  return recordStep(step, [msg]).then(() => {
27
28
  // this is backward compatibility as this event may be used somewhere
28
- event.emit(event.step.comment, msg);
29
- output.say(msg, `${color}`);
30
- });
29
+ event.emit(event.step.comment, msg)
30
+ output.say(msg, `${color}`)
31
+ })
31
32
  }
32
33
 
33
34
  /**
@@ -38,14 +39,16 @@ class Actor {
38
39
  * @inner
39
40
  */
40
41
  limitTime(timeout) {
41
- if (!store.timeouts) return this;
42
+ if (!store.timeouts) return this
42
43
 
43
- event.dispatcher.prependOnceListener(event.step.before, (step) => {
44
- output.log(`Timeout to ${step}: ${timeout}s`);
45
- step.setTimeout(timeout * 1000, Step.TIMEOUT_ORDER.codeLimitTime);
46
- });
44
+ console.log('I.limitTime() is deprecated, use step.timeout() instead')
47
45
 
48
- return this;
46
+ event.dispatcher.prependOnceListener(event.step.before, step => {
47
+ output.log(`Timeout to ${step}: ${timeout}s`)
48
+ step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
49
+ })
50
+
51
+ return this
49
52
  }
50
53
 
51
54
  /**
@@ -55,11 +58,9 @@ class Actor {
55
58
  * @inner
56
59
  */
57
60
  retry(opts) {
58
- if (opts === undefined) opts = 1;
59
- recorder.retry(opts);
60
- // remove retry once the step passed
61
- recorder.add(() => event.dispatcher.once(event.step.finished, () => recorder.retries.pop()));
62
- return this;
61
+ console.log('I.retry() is deprecated, use step.retry() instead')
62
+ retryStep(opts)
63
+ return this
63
64
  }
64
65
  }
65
66
 
@@ -69,93 +70,60 @@ class Actor {
69
70
  * Wraps helper methods into promises.
70
71
  * @ignore
71
72
  */
72
- module.exports = function (obj = {}) {
73
- if (!store.actor) {
74
- store.actor = new Actor();
75
- }
76
- const actor = store.actor;
77
-
78
- const translation = container.translation();
79
-
80
- if (Object.keys(obj).length > 0) {
81
- Object.keys(obj)
82
- .forEach(action => {
83
- const actionAlias = translation.actionAliasFor(action);
84
-
85
- const currentMethod = obj[action];
86
- const ms = new MetaStep('I', action);
87
- if (translation.loaded) {
88
- ms.name = actionAlias;
89
- ms.actor = translation.I;
90
- }
91
- ms.setContext(actor);
92
- actor[action] = actor[actionAlias] = ms.run.bind(ms, currentMethod);
93
- });
73
+ export default function (obj = {}, container) {
74
+ // Use global container if none provided
75
+ if (!container) {
76
+ container = Container
94
77
  }
78
+
79
+ const actor = container.actor() || new Actor()
95
80
 
96
- const helpers = container.helpers();
81
+ // load all helpers once container initialized
82
+ container.started(() => {
83
+ const translation = container.translation()
84
+ const helpers = container.helpers()
97
85
 
98
- // add methods from enabled helpers
99
- Object.values(helpers)
100
- .forEach((helper) => {
86
+ // add methods from enabled helpers
87
+ Object.values(helpers).forEach(helper => {
101
88
  methodsOfObject(helper, 'Helper')
102
89
  .filter(method => method !== 'constructor' && method[0] !== '_')
103
- .forEach((action) => {
104
- const actionAlias = translation.actionAliasFor(action);
90
+ .forEach(action => {
91
+ const actionAlias = translation.actionAliasFor(action)
105
92
  if (!actor[action]) {
106
93
  actor[action] = actor[actionAlias] = function () {
107
- const step = new Step(helper, action);
94
+ const step = new Step(helper, action)
108
95
  if (translation.loaded) {
109
- step.name = actionAlias;
110
- step.actor = translation.I;
96
+ step.name = actionAlias
97
+ step.actor = translation.I
111
98
  }
112
99
  // add methods to promise chain
113
- return recordStep(step, Array.from(arguments));
114
- };
100
+ return recordStep(step, Array.from(arguments))
101
+ }
115
102
  }
116
- });
117
- });
118
-
119
- return actor;
120
- };
121
-
122
- function recordStep(step, args) {
123
- step.status = 'queued';
124
- step.setArguments(args);
125
-
126
- // run async before step hooks
127
- event.emit(event.step.before, step);
128
-
129
- const task = `${step.name}: ${step.humanizeArgs()}`;
130
- let val;
131
-
132
- // run step inside promise
133
- recorder.add(task, () => {
134
- if (!step.startTime) { // step can be retries
135
- event.emit(event.step.started, step);
136
- step.startTime = Date.now();
137
- }
138
- return val = step.run(...args);
139
- }, false, undefined, step.getTimeout());
140
-
141
- event.emit(event.step.after, step);
142
-
143
- recorder.add('step passed', () => {
144
- step.endTime = Date.now();
145
- event.emit(event.step.passed, step, val);
146
- event.emit(event.step.finished, step);
147
- });
148
-
149
- recorder.catchWithoutStop((err) => {
150
- step.status = 'failed';
151
- step.endTime = Date.now();
152
- event.emit(event.step.failed, step);
153
- event.emit(event.step.finished, step);
154
- throw err;
155
- });
156
-
157
- recorder.add('return result', () => val);
158
- // run async after step hooks
159
-
160
- return recorder.promise();
103
+ })
104
+ })
105
+
106
+ // add translated custom steps from actor
107
+ Object.keys(obj).forEach(key => {
108
+ const actionAlias = translation.actionAliasFor(key)
109
+ if (!actor[actionAlias]) {
110
+ actor[actionAlias] = actor[key]
111
+ }
112
+ })
113
+
114
+ container.append({
115
+ support: {
116
+ I: actor,
117
+ },
118
+ })
119
+ })
120
+ // store.actor = actor;
121
+ // add custom steps from actor
122
+ Object.keys(obj).forEach(key => {
123
+ const ms = new MetaStep('I', key)
124
+ ms.setContext(actor)
125
+ actor[key] = ms.run.bind(ms, obj[key])
126
+ })
127
+
128
+ return actor
161
129
  }
package/lib/ai.js CHANGED
@@ -1,40 +1,45 @@
1
- const debug = require('debug')('codeceptjs:ai');
2
- const output = require('./output');
3
- const event = require('./event');
4
- const { removeNonInteractiveElements, minifyHtml, splitByChunks } = require('./html');
1
+ import debugModule from 'debug'
2
+ const debug = debugModule('codeceptjs:ai')
3
+ import output from './output.js'
4
+ import event from './event.js'
5
+ import { removeNonInteractiveElements, minifyHtml, splitByChunks } from './html.js'
5
6
 
6
7
  const defaultHtmlConfig = {
7
8
  maxLength: 50000,
8
9
  simplify: true,
9
10
  minify: true,
10
11
  html: {},
11
- };
12
+ }
12
13
 
13
14
  const defaultPrompts = {
14
- writeStep: (html, input) => [{
15
- role: 'user',
16
- content: `I am test engineer writing test in CodeceptJS
15
+ writeStep: (html, input) => [
16
+ {
17
+ role: 'user',
18
+ content: `I am test engineer writing test in CodeceptJS
17
19
  I have opened web page and I want to use CodeceptJS to ${input} on this page
18
20
  Provide me valid CodeceptJS code to accomplish it
19
21
  Use only locators from this HTML: \n\n${html}`,
20
- },
22
+ },
21
23
  ],
22
24
 
23
25
  healStep: (html, { step, error, prevSteps }) => {
24
- return [{
25
- role: 'user',
26
- content: `As a test automation engineer I am testing web application using CodeceptJS.
26
+ return [
27
+ {
28
+ role: 'user',
29
+ content: `As a test automation engineer I am testing web application using CodeceptJS.
27
30
  I want to heal a test that fails. Here is the list of executed steps: ${prevSteps.map(s => s.toString()).join(', ')}
28
31
  Propose how to adjust ${step.toCode()} step to fix the test.
29
32
  Use locators in order of preference: semantic locator by text, CSS, XPath. Use codeblocks marked with \`\`\`
30
33
  Here is the error message: ${error.message}
31
34
  Here is HTML code of a page where the failure has happened: \n\n${html}`,
32
- }];
35
+ },
36
+ ]
33
37
  },
34
38
 
35
- generatePageObject: (html, extraPrompt = '', rootLocator = null) => [{
36
- role: 'user',
37
- content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
39
+ generatePageObject: (html, extraPrompt = '', rootLocator = null) => [
40
+ {
41
+ role: 'user',
42
+ content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
38
43
  Here is an sample page object:
39
44
 
40
45
  const { I } = inject();
@@ -60,72 +65,73 @@ module.exports = {
60
65
  ${extraPrompt}
61
66
  ${rootLocator ? `All provided elements are inside '${rootLocator}'. Declare it as root variable and for every locator use locate(...).inside(root)` : ''}
62
67
  Add only locators from this HTML: \n\n${html}`,
63
- }],
64
- };
68
+ },
69
+ ],
70
+ }
65
71
 
66
72
  class AiAssistant {
67
73
  constructor() {
68
- this.totalTime = 0;
69
- this.numTokens = 0;
74
+ this.totalTime = 0
75
+ this.numTokens = 0
70
76
 
71
- this.reset();
72
- this.connectToEvents();
77
+ this.reset()
78
+ this.connectToEvents()
73
79
  }
74
80
 
75
81
  enable(config = {}) {
76
- debug('Enabling AI assistant');
77
- this.isEnabled = true;
82
+ debug('Enabling AI assistant')
83
+ this.isEnabled = true
78
84
 
79
- const { html, prompts, ...aiConfig } = config;
85
+ const { html, prompts, ...aiConfig } = config
80
86
 
81
- this.config = Object.assign(this.config, aiConfig);
82
- this.htmlConfig = Object.assign(defaultHtmlConfig, html);
83
- this.prompts = Object.assign(defaultPrompts, prompts);
87
+ this.config = Object.assign(this.config, aiConfig)
88
+ this.htmlConfig = Object.assign(defaultHtmlConfig, html)
89
+ this.prompts = Object.assign(defaultPrompts, prompts)
84
90
 
85
- debug('Config', this.config);
91
+ debug('Config', this.config)
86
92
  }
87
93
 
88
94
  reset() {
89
- this.numTokens = 0;
90
- this.isEnabled = false;
95
+ this.numTokens = 0
96
+ this.isEnabled = false
91
97
  this.config = {
92
98
  maxTokens: 1000000,
93
99
  request: null,
94
100
  response: parseCodeBlocks,
95
101
  // lets limit token usage to 1M
96
- };
97
- this.minifiedHtml = null;
98
- this.response = null;
99
- this.totalTime = 0;
102
+ }
103
+ this.minifiedHtml = null
104
+ this.response = null
105
+ this.totalTime = 0
100
106
  }
101
107
 
102
108
  disable() {
103
- this.isEnabled = false;
109
+ this.isEnabled = false
104
110
  }
105
111
 
106
112
  connectToEvents() {
107
113
  event.dispatcher.on(event.all.result, () => {
108
114
  if (this.isEnabled && this.numTokens > 0) {
109
- const numTokensK = Math.ceil(this.numTokens / 1000);
110
- const maxTokensK = Math.ceil(this.config.maxTokens / 1000);
111
- output.print(`AI assistant took ${this.totalTime}s and used ~${numTokensK}K input tokens. Tokens limit: ${maxTokensK}K`);
115
+ const numTokensK = Math.ceil(this.numTokens / 1000)
116
+ const maxTokensK = Math.ceil(this.config.maxTokens / 1000)
117
+ output.print(`AI assistant took ${this.totalTime}s and used ~${numTokensK}K input tokens. Tokens limit: ${maxTokensK}K`)
112
118
  }
113
- });
119
+ })
114
120
  }
115
121
 
116
122
  checkRequestFn() {
117
123
  if (!this.isEnabled) {
118
- debug('AI assistant is disabled');
119
- return;
124
+ debug('AI assistant is disabled')
125
+ return
120
126
  }
121
127
 
122
- if (this.config.request) return;
128
+ if (this.config.request) return
123
129
 
124
130
  const noRequestErrorMessage = `
125
- No request function is set for AI assistant.
126
- Please implement your own request function and set it in the config.
131
+ No request function is set for AI assistant.
127
132
 
128
- [!] 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.
133
+ [!] AI request was decoupled from CodeceptJS. To connect to OpenAI or other AI service.
134
+ Please implement your own request function and set it in the config.
129
135
 
130
136
  Example (connect to OpenAI):
131
137
 
@@ -134,82 +140,80 @@ class AiAssistant {
134
140
  const OpenAI = require('openai');
135
141
  const openai = new OpenAI({ apiKey: process.env['OPENAI_API_KEY'] })
136
142
  const response = await openai.chat.completions.create({
137
- model: 'gpt-3.5-turbo-0125',
143
+ model: 'gpt-4o-mini',
138
144
  messages,
139
145
  });
140
146
  return response?.data?.choices[0]?.message?.content;
141
147
  }
142
148
  }
143
- `.trim();
149
+ `.trim()
144
150
 
145
- throw new Error(noRequestErrorMessage);
151
+ throw new Error(noRequestErrorMessage)
146
152
  }
147
153
 
148
154
  async setHtmlContext(html) {
149
- let processedHTML = html;
155
+ let processedHTML = html
150
156
 
151
157
  if (this.htmlConfig.simplify) {
152
- processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig);
158
+ processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig)
153
159
  }
154
160
 
155
- if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML);
156
- if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0];
161
+ if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML)
162
+ if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0]
157
163
 
158
- this.minifiedHtml = processedHTML;
164
+ this.minifiedHtml = processedHTML
159
165
  }
160
166
 
161
167
  getResponse() {
162
- return this.response || '';
168
+ return this.response || ''
163
169
  }
164
170
 
165
171
  async createCompletion(messages) {
166
- if (!this.isEnabled) return '';
167
-
168
- debug('Request', messages);
169
-
170
- this.checkRequestFn();
171
-
172
- this.response = null;
173
-
174
- this.calculateTokens(messages);
172
+ if (!this.isEnabled) return ''
175
173
 
176
174
  try {
177
- const startTime = process.hrtime();
178
- this.response = await this.config.request(messages);
179
- const endTime = process.hrtime(startTime);
180
- const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9;
181
-
182
- this.totalTime += Math.round(executionTimeInSeconds);
183
- debug('AI response time', executionTimeInSeconds);
184
- debug('Response', this.response);
185
- this.stopWhenReachingTokensLimit();
186
- return this.response;
175
+ this.checkRequestFn()
176
+ debug('Request', messages)
177
+
178
+ this.response = null
179
+
180
+ this.calculateTokens(messages)
181
+ const startTime = process.hrtime()
182
+ this.response = await this.config.request(messages)
183
+ const endTime = process.hrtime(startTime)
184
+ const executionTimeInSeconds = endTime[0] + endTime[1] / 1e9
185
+
186
+ this.totalTime += Math.round(executionTimeInSeconds)
187
+ debug('AI response time', executionTimeInSeconds)
188
+ debug('Response', this.response)
189
+ this.stopWhenReachingTokensLimit()
190
+ return this.response
187
191
  } catch (err) {
188
- debug(err.response);
189
- output.print('');
190
- output.error(`AI service error: ${err.message}`);
191
- if (err?.response?.data?.error?.code) output.error(err?.response?.data?.error?.code);
192
- if (err?.response?.data?.error?.message) output.error(err?.response?.data?.error?.message);
193
- this.stopWhenReachingTokensLimit();
194
- return '';
192
+ debug(err.response)
193
+ output.print('')
194
+ output.error(`AI service error: ${err.message}`)
195
+ if (err?.response?.data?.error?.code) output.error(err?.response?.data?.error?.code)
196
+ if (err?.response?.data?.error?.message) output.error(err?.response?.data?.error?.message)
197
+ this.stopWhenReachingTokensLimit()
198
+ return ''
195
199
  }
196
200
  }
197
201
 
198
202
  async healFailedStep(failureContext) {
199
- if (!this.isEnabled) return [];
200
- if (!failureContext.html) throw new Error('No HTML context provided');
203
+ if (!this.isEnabled) return []
204
+ if (!failureContext.html) throw new Error('No HTML context provided')
201
205
 
202
- await this.setHtmlContext(failureContext.html);
206
+ await this.setHtmlContext(failureContext.html)
203
207
 
204
208
  if (!this.minifiedHtml) {
205
- debug('HTML context is empty after removing non-interactive elements & minification');
206
- return [];
209
+ debug('HTML context is empty after removing non-interactive elements & minification')
210
+ return []
207
211
  }
208
212
 
209
- const response = await this.createCompletion(this.prompts.healStep(this.minifiedHtml, failureContext));
210
- if (!response) return [];
213
+ const response = await this.createCompletion(this.prompts.healStep(this.minifiedHtml, failureContext))
214
+ if (!response) return []
211
215
 
212
- return this.config.response(response);
216
+ return this.config.response(response)
213
217
  }
214
218
 
215
219
  /**
@@ -219,13 +223,13 @@ class AiAssistant {
219
223
  * @returns
220
224
  */
221
225
  async generatePageObject(extraPrompt = null, locator = null) {
222
- if (!this.isEnabled) return [];
223
- 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')
224
228
 
225
- const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt));
226
- if (!response) return [];
229
+ const response = await this.createCompletion(this.prompts.generatePageObject(this.minifiedHtml, locator, extraPrompt))
230
+ if (!response) return []
227
231
 
228
- return this.config.response(response);
232
+ return this.config.response(response)
229
233
  }
230
234
 
231
235
  calculateTokens(messages) {
@@ -233,66 +237,72 @@ class AiAssistant {
233
237
  // this approach was tested via https://platform.openai.com/tokenizer
234
238
  // we need it to display current tokens usage so users could analyze effectiveness of AI
235
239
 
236
- const inputString = messages.map(m => m.content).join(' ').trim();
237
- const numWords = (inputString.match(/[^\s\-:=]+/g) || []).length;
240
+ const inputString = messages
241
+ .map(m => m.content)
242
+ .join(' ')
243
+ .trim()
244
+ const numWords = (inputString.match(/[^\s\-:=]+/g) || []).length
238
245
 
239
246
  // 2.5 token is constant for average HTML input
240
- const tokens = numWords * 2.5;
247
+ const tokens = numWords * 2.5
241
248
 
242
- this.numTokens += tokens;
249
+ this.numTokens += tokens
243
250
 
244
- return tokens;
251
+ return tokens
245
252
  }
246
253
 
247
254
  stopWhenReachingTokensLimit() {
248
- if (this.numTokens < this.config.maxTokens) return;
255
+ if (this.numTokens < this.config.maxTokens) return
249
256
 
250
- output.print(`AI assistant has reached the limit of ${this.config.maxTokens} tokens in this session. It will be disabled now`);
251
- this.disable();
257
+ output.print(`AI assistant has reached the limit of ${this.config.maxTokens} tokens in this session. It will be disabled now`)
258
+ this.disable()
252
259
  }
253
260
 
254
261
  async writeSteps(input) {
255
- if (!this.isEnabled) return;
256
- if (!this.minifiedHtml) throw new Error('No HTML context provided');
262
+ if (!this.isEnabled) return
263
+ if (!this.minifiedHtml) throw new Error('No HTML context provided')
257
264
 
258
- const snippets = [];
265
+ const snippets = []
259
266
 
260
- const response = await this.createCompletion(this.prompts.writeStep(this.minifiedHtml, input));
261
- if (!response) return;
262
- snippets.push(...this.config.response(response));
267
+ const response = await this.createCompletion(this.prompts.writeStep(this.minifiedHtml, input))
268
+ if (!response) return
269
+ snippets.push(...this.config.response(response))
263
270
 
264
- debug(snippets[0]);
271
+ debug(snippets[0])
265
272
 
266
- return snippets[0];
273
+ return snippets[0]
267
274
  }
268
275
  }
269
276
 
270
277
  function parseCodeBlocks(response) {
271
278
  // Regular expression pattern to match code snippets
272
- const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g;
279
+ const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g
273
280
 
274
281
  // Array to store extracted code snippets
275
- const codeSnippets = [];
282
+ const codeSnippets = []
276
283
 
277
- response = response.split('\n').map(line => line.trim()).join('\n');
284
+ response = response
285
+ .split('\n')
286
+ .map(line => line.trim())
287
+ .join('\n')
278
288
 
279
289
  // Iterate over matches and extract code snippets
280
- let match;
290
+ let match
281
291
  while ((match = codeSnippetPattern.exec(response)) !== null) {
282
- codeSnippets.push(match[1]);
292
+ codeSnippets.push(match[1])
283
293
  }
284
294
 
285
295
  // Remove "Scenario", "Feature", and "require()" lines
286
296
  const modifiedSnippets = codeSnippets.map(snippet => {
287
- const lines = snippet.split('\n');
297
+ const lines = snippet.split('\n')
288
298
 
289
- const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('));
299
+ const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('))
290
300
 
291
- return filteredLines.join('\n');
301
+ return filteredLines.join('\n')
292
302
  // remove snippets that move from current url
293
- }); // .filter(snippet => !line.includes('I.amOnPage'));
303
+ }) // .filter(snippet => !line.includes('I.amOnPage'));
294
304
 
295
- return modifiedSnippets.filter(snippet => !!snippet);
305
+ return modifiedSnippets.filter(snippet => !!snippet)
296
306
  }
297
307
 
298
- module.exports = new AiAssistant();
308
+ export default new AiAssistant()
@@ -1,11 +1,13 @@
1
- const Assertion = require('../assert')
2
- const AssertionFailedError = require('./error')
3
- const { template } = require('../utils')
4
- const output = require('../output')
1
+ import assertionModule from '../assert.js'
2
+ const Assertion = assertionModule.default || assertionModule
3
+ import AssertionFailedError from './error.js'
4
+ import { template } from '../utils.js'
5
+ import outputModule from '../output.js'
6
+ const output = outputModule.default || outputModule
5
7
 
6
8
  class EmptinessAssertion extends Assertion {
7
9
  constructor(params) {
8
- super((value) => {
10
+ super(value => {
9
11
  if (Array.isArray(value)) {
10
12
  return value.length === 0
11
13
  }
@@ -22,9 +24,7 @@ class EmptinessAssertion extends Assertion {
22
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}}'))
27
+ const msg = err.template.replace('{{value}}', output.colors.bold('{{value}}')).replace('{{subject}}', output.colors.bold('{{subject}}'))
28
28
  return template(msg, this.params)
29
29
  }
30
30
  return err
@@ -37,7 +37,6 @@ class EmptinessAssertion extends Assertion {
37
37
  }
38
38
  }
39
39
 
40
- module.exports = {
41
- Assertion: EmptinessAssertion,
42
- empty: (subject) => new EmptinessAssertion({ subject }),
43
- }
40
+ export { EmptinessAssertion as Assertion }
41
+
42
+ export const empty = subject => new EmptinessAssertion({ subject })