codeceptjs 4.0.0-beta.2 → 4.0.0-beta.20

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 (209) 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 +262 -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 +301 -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 +109 -50
  39. package/lib/container.js +641 -261
  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/loaderCheck.js +124 -0
  157. package/lib/utils/mask_data.js +47 -0
  158. package/lib/utils/typescript.js +237 -0
  159. package/lib/utils.js +411 -228
  160. package/lib/workerStorage.js +37 -34
  161. package/lib/workers.js +532 -296
  162. package/package.json +124 -95
  163. package/translations/de-DE.js +5 -3
  164. package/translations/fr-FR.js +5 -4
  165. package/translations/index.js +22 -12
  166. package/translations/it-IT.js +4 -3
  167. package/translations/ja-JP.js +4 -3
  168. package/translations/nl-NL.js +76 -0
  169. package/translations/pl-PL.js +4 -3
  170. package/translations/pt-BR.js +4 -3
  171. package/translations/ru-RU.js +4 -3
  172. package/translations/utils.js +10 -0
  173. package/translations/zh-CN.js +4 -3
  174. package/translations/zh-TW.js +4 -3
  175. package/typings/index.d.ts +546 -185
  176. package/typings/promiseBasedTypes.d.ts +150 -875
  177. package/typings/types.d.ts +547 -992
  178. package/lib/cli.js +0 -249
  179. package/lib/dirname.js +0 -5
  180. package/lib/helper/Expect.js +0 -425
  181. package/lib/helper/ExpectHelper.js +0 -399
  182. package/lib/helper/MockServer.js +0 -223
  183. package/lib/helper/Nightmare.js +0 -1411
  184. package/lib/helper/Protractor.js +0 -1835
  185. package/lib/helper/SoftExpectHelper.js +0 -381
  186. package/lib/helper/TestCafe.js +0 -1410
  187. package/lib/helper/clientscripts/nightmare.js +0 -213
  188. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  189. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  190. package/lib/interfaces/bdd.js +0 -98
  191. package/lib/interfaces/featureConfig.js +0 -69
  192. package/lib/interfaces/gherkin.js +0 -195
  193. package/lib/listener/artifacts.js +0 -19
  194. package/lib/listener/retry.js +0 -68
  195. package/lib/listener/timeout.js +0 -109
  196. package/lib/mochaFactory.js +0 -110
  197. package/lib/plugin/allure.js +0 -15
  198. package/lib/plugin/commentStep.js +0 -136
  199. package/lib/plugin/debugErrors.js +0 -67
  200. package/lib/plugin/eachElement.js +0 -127
  201. package/lib/plugin/fakerTransform.js +0 -49
  202. package/lib/plugin/retryTo.js +0 -121
  203. package/lib/plugin/selenoid.js +0 -371
  204. package/lib/plugin/standardActingHelpers.js +0 -9
  205. package/lib/plugin/tryTo.js +0 -105
  206. package/lib/plugin/wdio.js +0 -246
  207. package/lib/scenario.js +0 -222
  208. package/lib/ui.js +0 -238
  209. package/lib/within.js +0 -70
package/lib/heal.js CHANGED
@@ -1,126 +1,125 @@
1
- import debug from 'debug';
2
- import colors from 'chalk';
3
- import Container from './container.js';
4
- import recorder from './recorder.js';
5
- import * as event from './event.js';
6
- import { output } from './output.js';
7
-
8
- const logger = debug('myapp:server');
9
-
10
- logger('codeceptjs:heal');
1
+ import debugModule from 'debug'
2
+ const debug = debugModule('codeceptjs:heal')
3
+ import colors from 'chalk'
4
+ import recorder from './recorder.js'
5
+ import output from './output.js'
6
+ import event from './event.js'
11
7
 
12
8
  /**
13
9
  * @class
14
10
  */
15
11
  class Heal {
16
12
  constructor() {
17
- this.recipes = {};
18
- this.fixes = [];
19
- this.prepareFns = [];
20
- this.contextName = null;
21
- this.numHealed = 0;
13
+ this.recipes = {}
14
+ this.fixes = []
15
+ this.prepareFns = []
16
+ this.contextName = null
17
+ this.numHealed = 0
22
18
  }
23
19
 
24
20
  clear() {
25
- this.recipes = {};
26
- this.fixes = [];
27
- this.prepareFns = [];
28
- this.contextName = null;
29
- this.numHealed = 0;
21
+ this.recipes = {}
22
+ this.fixes = []
23
+ this.prepareFns = []
24
+ this.contextName = null
25
+ this.numHealed = 0
30
26
  }
31
27
 
32
28
  addRecipe(name, opts = {}) {
33
- if (!opts.priority) opts.priority = 0;
29
+ if (!opts.priority) opts.priority = 0
34
30
 
35
- if (!opts.fn) throw new Error(`Recipe ${name} should have a function 'fn' to execute`);
31
+ if (!opts.fn) throw new Error(`Recipe ${name} should have a function 'fn' to execute`)
36
32
 
37
- this.recipes[name] = opts;
33
+ this.recipes[name] = opts
38
34
  }
39
35
 
40
36
  connectToEvents() {
41
- event.dispatcher.on(event.suite.before, (suite) => {
42
- this.contextName = suite.title;
43
- });
37
+ event.dispatcher.on(event.suite.before, suite => {
38
+ this.contextName = suite.title
39
+ })
44
40
 
45
- event.dispatcher.on(event.test.started, (test) => {
46
- this.contextName = test.fullTitle();
47
- });
41
+ event.dispatcher.on(event.test.started, test => {
42
+ this.contextName = test.fullTitle()
43
+ })
48
44
 
49
45
  event.dispatcher.on(event.test.finished, () => {
50
- this.contextName = null;
51
- });
46
+ this.contextName = null
47
+ })
52
48
  }
53
49
 
54
50
  hasCorrespondingRecipes(step) {
55
- return matchRecipes(this.recipes, this.contextName)
56
- .filter(r => !r.steps || r.steps.includes(step.name))
57
- .length > 0;
51
+ return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.name)).length > 0
58
52
  }
59
53
 
60
54
  async getCodeSuggestions(context) {
61
- const suggestions = [];
62
- const recipes = matchRecipes(this.recipes, this.contextName);
55
+ const suggestions = []
56
+ const recipes = matchRecipes(this.recipes, this.contextName)
63
57
 
64
- logger('Recipes', recipes);
58
+ debug('Recipes', recipes)
65
59
 
66
- const currentOutputLevel = output.level();
67
- output.level(0);
60
+ const currentOutputLevel = output.level()
61
+ output.level(0)
68
62
 
69
- for (const [property, prepareFn] of Object.entries(recipes.map(r => r.prepare).filter(p => !!p).reduce((acc, obj) => ({ ...acc, ...obj }), {}))) {
70
- if (!prepareFn) continue;
63
+ for (const [property, prepareFn] of Object.entries(
64
+ recipes
65
+ .map(r => r.prepare)
66
+ .filter(p => !!p)
67
+ .reduce((acc, obj) => ({ ...acc, ...obj }), {}),
68
+ )) {
69
+ if (!prepareFn) continue
71
70
 
72
- if (context[property]) continue;
73
- context[property] = await prepareFn(Container.support());
71
+ if (context[property]) continue
72
+ context[property] = await prepareFn(global.inject())
74
73
  }
75
74
 
76
- output.level(currentOutputLevel);
75
+ output.level(currentOutputLevel)
77
76
 
78
77
  for (const recipe of recipes) {
79
- let snippets = await recipe.fn(context);
80
- if (!Array.isArray(snippets)) snippets = [snippets];
78
+ let snippets = await recipe.fn(context)
79
+ if (!Array.isArray(snippets)) snippets = [snippets]
81
80
 
82
81
  suggestions.push({
83
82
  name: recipe.name,
84
83
  snippets,
85
- });
84
+ })
86
85
  }
87
86
 
88
- return suggestions.filter(s => !isBlank(s.snippets));
87
+ return suggestions.filter(s => !isBlank(s.snippets))
89
88
  }
90
89
 
91
90
  async healStep(failedStep, error, failureContext = {}) {
92
- output.debug(`Trying to heal ${failedStep.toCode()} step`);
91
+ output.debug(`Trying to heal ${failedStep.toCode()} step`)
93
92
 
94
93
  Object.assign(failureContext, {
95
94
  error,
96
95
  step: failedStep,
97
96
  prevSteps: failureContext?.test?.steps?.slice(0, -1) || [],
98
- });
97
+ })
99
98
 
100
- const suggestions = await this.getCodeSuggestions(failureContext);
99
+ const suggestions = await this.getCodeSuggestions(failureContext)
101
100
 
102
101
  if (suggestions.length === 0) {
103
- logger('No healing suggestions found');
104
- throw error;
102
+ debug('No healing suggestions found')
103
+ throw error
105
104
  }
106
105
 
107
- output.debug(`Received ${suggestions.length} suggestion${suggestions.length === 1 ? '' : 's'}`);
106
+ output.debug(`Received ${suggestions.length} suggestion${suggestions.length === 1 ? '' : 's'}`)
108
107
 
109
- logger(suggestions);
108
+ debug(suggestions)
110
109
 
111
110
  for (const suggestion of suggestions) {
112
111
  for (const codeSnippet of suggestion.snippets) {
113
112
  try {
114
- logger('Executing', codeSnippet);
115
- recorder.catch((e) => {
116
- logger(e);
117
- });
113
+ debug('Executing', codeSnippet)
114
+ recorder.catch(e => {
115
+ debug(e)
116
+ })
118
117
 
119
118
  if (typeof codeSnippet === 'string') {
120
- const I = Container.support('I'); // eslint-disable-line
121
- await eval(codeSnippet); // eslint-disable-line
119
+ const I = global.container.support('I')
120
+ await eval(codeSnippet)
122
121
  } else if (typeof codeSnippet === 'function') {
123
- await codeSnippet(Container.support());
122
+ await codeSnippet(global.container.support())
124
123
  }
125
124
 
126
125
  this.fixes.push({
@@ -128,49 +127,54 @@ class Heal {
128
127
  test: failureContext?.test,
129
128
  step: failedStep,
130
129
  snippet: codeSnippet,
131
- });
130
+ })
131
+
132
+ if (failureContext?.test) {
133
+ const test = failureContext.test
134
+ let note = `This test was healed by '${suggestion.name}'`
135
+ note += `\n\nReplace the failed code:\n\n`
136
+ note += colors.red(`- ${failedStep.toCode()}\n`)
137
+ note += colors.green(`+ ${codeSnippet}\n`)
138
+ test.addNote('heal', note)
139
+ test.meta.healed = true
140
+ }
132
141
 
133
- recorder.add('healed', () => output.print(colors.bold.green(` Code healed successfully by ${suggestion.name}`), colors.gray('(no errors thrown)')));
134
- this.numHealed++;
142
+ recorder.add('healed', () => output.print(colors.bold.green(` Code healed successfully by ${suggestion.name}`), colors.gray('(no errors thrown)')))
143
+ this.numHealed++
135
144
  // recorder.session.restore();
136
- return;
145
+ return
137
146
  } catch (err) {
138
- logger('Failed to execute code', err);
139
- recorder.ignoreErr(err); // healing did not help
140
- recorder.catchWithoutStop(err);
141
- await recorder.promise(); // wait for all promises to resolve
147
+ debug('Failed to execute code', err)
148
+ recorder.ignoreErr(err) // healing did not help
149
+ recorder.catchWithoutStop(err)
150
+ await recorder.promise() // wait for all promises to resolve
142
151
  }
143
152
  }
144
153
  }
145
- output.debug(`Couldn't heal the code for ${failedStep.toCode()}`);
146
- recorder.throw(error);
154
+ output.debug(`Couldn't heal the code for ${failedStep.toCode()}`)
155
+ recorder.throw(error)
147
156
  }
148
157
 
149
- static setDefaultHealers() {
150
- require('./template/heal');
158
+ static async setDefaultHealers() {
159
+ await import('./template/heal.js')
151
160
  }
152
161
  }
153
162
 
154
- const heal = new Heal();
163
+ const heal = new Heal()
155
164
 
156
- export { heal };
165
+ export default heal
157
166
 
158
167
  function matchRecipes(recipes, contextName) {
159
168
  return Object.entries(recipes)
160
169
  .filter(([, recipe]) => !contextName || !recipe.grep || new RegExp(recipe.grep).test(contextName))
161
170
  .sort(([, a], [, b]) => a.priority - b.priority)
162
171
  .map(([name, recipe]) => {
163
- recipe.name = name;
164
- return recipe;
172
+ recipe.name = name
173
+ return recipe
165
174
  })
166
- .filter(r => !!r.fn);
175
+ .filter(r => !!r.fn)
167
176
  }
168
177
 
169
178
  function isBlank(value) {
170
- return (
171
- value == null
172
- || (Array.isArray(value) && value.length === 0)
173
- || (typeof value === 'object' && Object.keys(value).length === 0)
174
- || (typeof value === 'string' && value.trim() === '')
175
- );
179
+ return value == null || (Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0) || (typeof value === 'string' && value.trim() === '')
176
180
  }
package/lib/helper/AI.js CHANGED
@@ -1,39 +1,53 @@
1
- import Helper from '@codeceptjs/helper';
2
- import AiAssistant from '../ai.js';
3
- import standardActingHelpers from '../plugin/standardActingHelpers.js';
4
- import Container from '../container.js';
5
- import { splitByChunks, minifyHtml } from '../html.js';
1
+ import HelperModule from '@codeceptjs/helper'
2
+ import ora from 'ora-classic'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import ai from '../ai.js'
6
+ import Container from '../container.js'
7
+ import { splitByChunks, minifyHtml } from '../html.js'
8
+ import { beautify } from '../utils.js'
9
+ import output from '../output.js'
10
+ import { registerVariable } from '../pause.js'
11
+
12
+ const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
13
+
14
+ const gtpRole = {
15
+ user: 'user',
16
+ }
6
17
 
7
18
  /**
8
19
  * AI Helper for CodeceptJS.
9
20
  *
10
21
  * This helper class provides integration with the AI GPT-3.5 or 4 language model for generating responses to questions or prompts within the context of web pages. It allows you to interact with the GPT-3.5 model to obtain intelligent responses based on HTML fragments or general prompts.
11
- * This helper should be enabled with any web helpers like Playwright or Puppeteer or WebDrvier to ensure the HTML context is available.
22
+ * This helper should be enabled with any web helpers like Playwright or Puppeteer or WebDriver to ensure the HTML context is available.
23
+ *
24
+ * Use it only in development mode. It is recommended to run it only inside pause() mode.
12
25
  *
13
26
  * ## Configuration
14
27
  *
15
- * This helper should be configured in codecept.json or codecept.conf.js
28
+ * This helper should be configured in codecept.conf.{js|ts}
16
29
  *
17
30
  * * `chunkSize`: (optional, default: 80000) - The maximum number of characters to send to the AI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4.
18
31
  */
19
32
  class AI extends Helper {
20
33
  constructor(config) {
21
- super(config);
22
- this.aiAssistant = AiAssistant;
34
+ super(config)
35
+ this.aiAssistant = ai
23
36
 
24
37
  this.options = {
25
38
  chunkSize: 80000,
26
- };
27
- this.options = { ...this.options, ...config };
39
+ }
40
+ this.options = { ...this.options, ...config }
41
+ this.aiAssistant.enable(this.config)
28
42
  }
29
43
 
30
44
  _beforeSuite() {
31
- const helpers = Container.helpers();
45
+ const helpers = Container.helpers()
32
46
 
33
47
  for (const helperName of standardActingHelpers) {
34
48
  if (Object.keys(helpers).indexOf(helperName) > -1) {
35
- this.helper = helpers[helperName];
36
- break;
49
+ this.helper = helpers[helperName]
50
+ break
37
51
  }
38
52
  }
39
53
  }
@@ -50,30 +64,34 @@ class AI extends Helper {
50
64
  * @returns {Promise<string>} - A Promise that resolves to the generated responses from the GPT model, joined by newlines.
51
65
  */
52
66
  async askGptOnPage(prompt) {
53
- const html = await this.helper.grabSource();
67
+ const html = await this.helper.grabSource()
54
68
 
55
- const htmlChunks = splitByChunks(html, this.options.chunkSize);
69
+ const htmlChunks = splitByChunks(html, this.options.chunkSize)
56
70
 
57
- if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`);
71
+ if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`)
58
72
 
59
- const responses = [];
73
+ const responses = []
60
74
 
61
75
  for (const chunk of htmlChunks) {
62
76
  const messages = [
63
- { role: 'user', content: prompt },
64
- { role: 'user', content: `Within this HTML: ${minifyHtml(chunk)}` },
65
- ];
77
+ { role: gtpRole.user, content: prompt },
78
+ { role: gtpRole.user, content: `Within this HTML: ${await minifyHtml(chunk)}` },
79
+ ]
66
80
 
67
- if (htmlChunks.length > 1) messages.push({ role: 'user', content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment' });
81
+ if (htmlChunks.length > 1)
82
+ messages.push({
83
+ role: 'user',
84
+ content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment',
85
+ })
68
86
 
69
- const response = await this.aiAssistant.createCompletion(messages);
87
+ const response = await this._processAIRequest(messages)
70
88
 
71
- console.log(response);
89
+ output.print(response)
72
90
 
73
- responses.push(response);
91
+ responses.push(response)
74
92
  }
75
93
 
76
- return responses.join('\n\n');
94
+ return responses.join('\n\n')
77
95
  }
78
96
 
79
97
  /**
@@ -89,36 +107,108 @@ class AI extends Helper {
89
107
  * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
90
108
  */
91
109
  async askGptOnPageFragment(prompt, locator) {
92
- const html = await this.helper.grabHTMLFrom(locator);
110
+ const html = await this.helper.grabHTMLFrom(locator)
93
111
 
94
112
  const messages = [
95
- { role: 'user', content: prompt },
96
- { role: 'user', content: `Within this HTML: ${minifyHtml(html)}` },
97
- ];
113
+ { role: gtpRole.user, content: prompt },
114
+ { role: gtpRole.user, content: `Within this HTML: ${await minifyHtml(html)}` },
115
+ ]
98
116
 
99
- const response = await this.aiAssistant.createCompletion(messages);
117
+ const response = await this._processAIRequest(messages)
100
118
 
101
- console.log(response);
119
+ output.print(response)
102
120
 
103
- return response;
121
+ return response
104
122
  }
105
123
 
106
124
  /**
107
- * Send a general request to ChatGPT and return response.
125
+ * Send a general request to AI and return response.
108
126
  * @param {string} prompt
109
127
  * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
110
128
  */
111
129
  async askGptGeneralPrompt(prompt) {
112
- const messages = [
113
- { role: 'user', content: prompt },
114
- ];
130
+ const messages = [{ role: gtpRole.user, content: prompt }]
131
+
132
+ const response = await this._processAIRequest(messages)
133
+
134
+ output.print(response)
135
+
136
+ return response
137
+ }
138
+
139
+ /**
140
+ * Generates PageObject for current page using AI.
141
+ *
142
+ * It saves the PageObject to the output directory. You can review the page object and adjust it as needed and move to pages directory.
143
+ * Prompt can be customized in a global config file.
144
+ *
145
+ * ```js
146
+ * // create page object for whole page
147
+ * I.askForPageObject('home');
148
+ *
149
+ * // create page object with extra prompt
150
+ * I.askForPageObject('home', 'implement signIn(username, password) method');
151
+ *
152
+ * // create page object for a specific element
153
+ * I.askForPageObject('home', null, '.detail');
154
+ * ```
155
+ *
156
+ * Asks for a page object based on the provided page name, locator, and extra prompt.
157
+ *
158
+ * @async
159
+ * @param {string} pageName - The name of the page to retrieve the object for.
160
+ * @param {string|null} [extraPrompt=null] - An optional extra prompt for additional context or information.
161
+ * @param {string|null} [locator=null] - An optional locator to find a specific element on the page.
162
+ * @returns {Promise<Object>} A promise that resolves to the requested page object.
163
+ */
164
+ async askForPageObject(pageName, extraPrompt = null, locator = null) {
165
+ const spinner = ora(' Processing AI request...').start()
166
+
167
+ try {
168
+ const html = locator ? await this.helper.grabHTMLFrom(locator) : await this.helper.grabSource()
169
+ await this.aiAssistant.setHtmlContext(html)
170
+ const response = await this.aiAssistant.generatePageObject(extraPrompt, locator)
171
+ spinner.stop()
172
+
173
+ if (!response[0]) {
174
+ output.error('No response from AI')
175
+ return ''
176
+ }
115
177
 
116
- const response = await this.aiAssistant.createCompletion(messages);
178
+ const code = beautify(response[0])
117
179
 
118
- console.log(response);
180
+ output.print('----- Generated PageObject ----')
181
+ output.print(code)
182
+ output.print('-------------------------------')
183
+
184
+ const fileName = path.join(output_dir, `${pageName}Page-${Date.now()}.js`)
185
+
186
+ output.print(output.styles.bold(`Page object for ${pageName} is saved to ${output.styles.bold(fileName)}`))
187
+ fs.writeFileSync(fileName, code)
188
+
189
+ try {
190
+ registerVariable('page', require(fileName))
191
+ output.success('Page object registered for this session as `page` variable')
192
+ output.print('Use `=>page.methodName()` in shell to run methods of page object')
193
+ output.print('Use `click(page.locatorName)` to check locators of page object')
194
+ } catch (err) {
195
+ output.error('Error while registering page object')
196
+ output.error(err.message)
197
+ }
198
+
199
+ return code
200
+ } catch (e) {
201
+ spinner.stop()
202
+ throw Error(`Something went wrong! ${e.message}`)
203
+ }
204
+ }
119
205
 
120
- return response;
206
+ async _processAIRequest(messages) {
207
+ const spinner = ora(' Processing AI request...').start()
208
+ const response = await this.aiAssistant.createCompletion(messages)
209
+ spinner.stop()
210
+ return response
121
211
  }
122
212
  }
123
213
 
124
- export default AI;
214
+ export default AI