codeceptjs 3.4.1 → 3.5.1

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 (75) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +11 -9
  3. package/bin/codecept.js +1 -1
  4. package/docs/ai.md +248 -0
  5. package/docs/build/Appium.js +47 -7
  6. package/docs/build/JSONResponse.js +4 -4
  7. package/docs/build/Nightmare.js +3 -1
  8. package/docs/build/OpenAI.js +122 -0
  9. package/docs/build/Playwright.js +234 -54
  10. package/docs/build/Protractor.js +3 -1
  11. package/docs/build/Puppeteer.js +101 -12
  12. package/docs/build/REST.js +15 -5
  13. package/docs/build/TestCafe.js +61 -2
  14. package/docs/build/WebDriver.js +85 -5
  15. package/docs/changelog.md +85 -0
  16. package/docs/helpers/Appium.md +152 -147
  17. package/docs/helpers/JSONResponse.md +4 -4
  18. package/docs/helpers/Nightmare.md +2 -0
  19. package/docs/helpers/OpenAI.md +70 -0
  20. package/docs/helpers/Playwright.md +228 -151
  21. package/docs/helpers/Puppeteer.md +153 -101
  22. package/docs/helpers/REST.md +6 -5
  23. package/docs/helpers/TestCafe.md +97 -49
  24. package/docs/helpers/WebDriver.md +159 -107
  25. package/docs/mobile.md +49 -2
  26. package/docs/parallel.md +56 -0
  27. package/docs/plugins.md +87 -33
  28. package/docs/secrets.md +6 -0
  29. package/docs/tutorial.md +2 -2
  30. package/docs/webapi/appendField.mustache +2 -0
  31. package/docs/webapi/blur.mustache +17 -0
  32. package/docs/webapi/focus.mustache +12 -0
  33. package/docs/webapi/type.mustache +3 -0
  34. package/lib/ai.js +171 -0
  35. package/lib/cli.js +10 -2
  36. package/lib/codecept.js +4 -0
  37. package/lib/command/dryRun.js +9 -1
  38. package/lib/command/generate.js +46 -3
  39. package/lib/command/init.js +23 -1
  40. package/lib/command/interactive.js +15 -1
  41. package/lib/command/run-workers.js +2 -1
  42. package/lib/container.js +13 -3
  43. package/lib/event.js +2 -0
  44. package/lib/helper/Appium.js +45 -7
  45. package/lib/helper/JSONResponse.js +4 -4
  46. package/lib/helper/Nightmare.js +1 -1
  47. package/lib/helper/OpenAI.js +122 -0
  48. package/lib/helper/Playwright.js +200 -45
  49. package/lib/helper/Protractor.js +1 -1
  50. package/lib/helper/Puppeteer.js +67 -12
  51. package/lib/helper/REST.js +15 -5
  52. package/lib/helper/TestCafe.js +30 -2
  53. package/lib/helper/WebDriver.js +51 -5
  54. package/lib/helper/scripts/blurElement.js +17 -0
  55. package/lib/helper/scripts/focusElement.js +17 -0
  56. package/lib/helper/scripts/highlightElement.js +20 -0
  57. package/lib/html.js +258 -0
  58. package/lib/interfaces/gherkin.js +8 -0
  59. package/lib/listener/retry.js +2 -1
  60. package/lib/pause.js +73 -17
  61. package/lib/plugin/debugErrors.js +67 -0
  62. package/lib/plugin/fakerTransform.js +4 -6
  63. package/lib/plugin/heal.js +177 -0
  64. package/lib/plugin/screenshotOnFail.js +11 -2
  65. package/lib/recorder.js +11 -8
  66. package/lib/secret.js +5 -4
  67. package/lib/step.js +6 -1
  68. package/lib/ui.js +4 -3
  69. package/lib/utils.js +17 -0
  70. package/lib/workers.js +57 -9
  71. package/package.json +25 -16
  72. package/translations/ja-JP.js +9 -9
  73. package/typings/index.d.ts +43 -9
  74. package/typings/promiseBasedTypes.d.ts +242 -25
  75. package/typings/types.d.ts +260 -35
@@ -7,6 +7,7 @@ const store = require('../store');
7
7
  const Container = require('../container');
8
8
 
9
9
  module.exports = async function (test, options) {
10
+ if (options.grep) process.env.grep = options.grep.toLowerCase();
10
11
  const configFile = options.config;
11
12
  let codecept;
12
13
 
@@ -57,9 +58,16 @@ function printTests(files) {
57
58
 
58
59
  let numOfTests = 0;
59
60
  let numOfSuites = 0;
61
+ const filteredSuites = [];
60
62
 
61
63
  for (const suite of mocha.suite.suites) {
62
- output.print(`${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')}`);
64
+ if (process.env.grep && suite.title.toLowerCase().includes(process.env.grep)) {
65
+ filteredSuites.push(suite);
66
+ }
67
+ }
68
+ const displayedSuites = process.env.grep ? filteredSuites : mocha.suite.suites;
69
+ for (const suite of displayedSuites) {
70
+ output.print(`${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${suite.tests.length} tests`);
63
71
  numOfSuites++;
64
72
 
65
73
  for (const test of suite.tests) {
@@ -24,6 +24,7 @@ Scenario('test something', async ({ {{actor}} }) => {
24
24
  // generates empty test
25
25
  module.exports.test = function (genPath) {
26
26
  const testsPath = getTestRoot(genPath);
27
+ global.codecept_dir = testsPath;
27
28
  const config = getConfig(testsPath);
28
29
  if (!config) return;
29
30
 
@@ -83,6 +84,29 @@ module.exports = {
83
84
  }
84
85
  `;
85
86
 
87
+ const poModuleTemplateTS = `const { I } = inject();
88
+
89
+ export = {
90
+
91
+ // insert your locators and methods here
92
+ }
93
+ `;
94
+
95
+ const poClassTemplate = `const { I } = inject();
96
+
97
+ class {{name}} {
98
+ constructor() {
99
+ //insert your locators
100
+ // this.button = '#button'
101
+ }
102
+ // insert your methods here
103
+ }
104
+
105
+ // For inheritance
106
+ module.exports = new {{name}}();
107
+ export = {{name}};
108
+ `;
109
+
86
110
  module.exports.pageObject = function (genPath, opts) {
87
111
  const testsPath = getTestRoot(genPath);
88
112
  const config = getConfig(testsPath);
@@ -110,7 +134,15 @@ module.exports.pageObject = function (genPath, opts) {
110
134
  name: 'filename',
111
135
  message: 'Where should it be stored',
112
136
  default: answers => `./${kind}s/${answers.name}.${extension}`,
113
- }]).then((result) => {
137
+ },
138
+ {
139
+ type: 'list',
140
+ name: 'objectType',
141
+ message: 'What is your preferred object type',
142
+ choices: ['module', 'class'],
143
+ default: 'module',
144
+ },
145
+ ]).then((result) => {
114
146
  const pageObjectFile = path.join(testsPath, result.filename);
115
147
  const dir = path.dirname(pageObjectFile);
116
148
  if (!fileExists(dir)) fs.mkdirSync(dir);
@@ -124,13 +156,24 @@ module.exports.pageObject = function (genPath, opts) {
124
156
  }
125
157
  actor = `require('${actorPath}')`;
126
158
  }
127
- if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return;
159
+
128
160
  const name = lcfirst(result.name) + ucfirst(kind);
161
+ if (result.objectType === 'module' && extension === 'ts') {
162
+ if (!safeFileWrite(pageObjectFile, poModuleTemplateTS.replace('{{actor}}', actor))) return;
163
+ } else if (result.objectType === 'module' && extension === 'js') {
164
+ if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return;
165
+ } else if (result.objectType === 'class') {
166
+ const content = poClassTemplate.replace(/{{actor}}/g, actor).replace(/{{name}}/g, name);
167
+ if (!safeFileWrite(pageObjectFile, content)) return;
168
+ }
169
+
129
170
  let data = readConfig(configFile);
130
171
  config.include[name] = result.filename;
172
+
173
+ if (!data) throw Error('Config file is empty');
131
174
  const currentInclude = `${data.match(/include:[\s\S][^\}]*/i)[0]}\n ${name}:${JSON.stringify(config.include[name])}`;
132
175
 
133
- data = data.replace(/include:[\s\S][^\}]*/i, `${currentInclude}`);
176
+ data = data.replace(/include:[\s\S][^\}]*/i, `${currentInclude},`);
134
177
 
135
178
  fs.writeFileSync(configFile, beautify(data), 'utf-8');
136
179
 
@@ -55,6 +55,18 @@ module.exports = function() {
55
55
  }
56
56
  `;
57
57
 
58
+ const defaultActorTs = `// in this file you can append custom step methods to 'I' object
59
+
60
+ export = function() {
61
+ return actor({
62
+
63
+ // Define custom steps here, use 'this' to access default methods of I.
64
+ // It is recommended to place a general 'login' function here.
65
+
66
+ });
67
+ }
68
+ `;
69
+
58
70
  module.exports = function (initPath) {
59
71
  const testsPath = getTestRoot(initPath);
60
72
 
@@ -188,7 +200,7 @@ module.exports = function (initPath) {
188
200
  // create steps file by default
189
201
  // no extra step file for typescript (as it doesn't match TS conventions)
190
202
  const stepFile = `./steps_file.${extension}`;
191
- fs.writeFileSync(path.join(testsPath, stepFile), defaultActor);
203
+ fs.writeFileSync(path.join(testsPath, stepFile), extension === 'ts' ? defaultActorTs : defaultActor);
192
204
  config.include.I = isTypeScript === true ? './steps_file' : stepFile;
193
205
  print(`Steps file created at ${stepFile}`);
194
206
 
@@ -305,6 +317,16 @@ module.exports = function (initPath) {
305
317
 
306
318
  print('Configure helpers...');
307
319
  inquirer.prompt(helperConfigs).then((helperResult) => {
320
+ if (helperResult.Playwright_browser === 'electron') {
321
+ delete helperResult.Playwright_url;
322
+ delete helperResult.Playwright_show;
323
+
324
+ helperResult.Playwright_electron = {
325
+ executablePath: '// require("electron") or require("electron-forge")',
326
+ args: ['path/to/your/main.js'],
327
+ };
328
+ }
329
+
308
330
  Object.keys(helperResult).forEach((key) => {
309
331
  const parts = key.split('_');
310
332
  const helperName = parts[0];
@@ -1,8 +1,10 @@
1
1
  const { getConfig, getTestRoot } = require('./utils');
2
2
  const recorder = require('../recorder');
3
3
  const Codecept = require('../codecept');
4
+ const Container = require('../container');
4
5
  const event = require('../event');
5
6
  const output = require('../output');
7
+ const webHelpers = require('../plugin/standardActingHelpers');
6
8
 
7
9
  module.exports = async function (path, options) {
8
10
  // Backward compatibility for --profile
@@ -29,9 +31,21 @@ module.exports = async function (path, options) {
29
31
  });
30
32
  event.emit(event.test.before, {
31
33
  title: '',
34
+ artifacts: {},
32
35
  });
36
+
37
+ const enabledHelpers = Container.helpers();
38
+ for (const helperName of Object.keys(enabledHelpers)) {
39
+ if (webHelpers.includes(helperName)) {
40
+ const I = enabledHelpers[helperName];
41
+ recorder.add(() => I.amOnPage('/'));
42
+ recorder.catchWithoutStop(e => output.print(`Error while loading home page: ${e.message}}`));
43
+ break;
44
+ }
45
+ }
33
46
  require('../pause')();
34
- recorder.add(() => event.emit(event.test.after));
47
+ // recorder.catchWithoutStop((err) => console.log(err.stack));
48
+ recorder.add(() => event.emit(event.test.after, {}));
35
49
  recorder.add(() => event.emit(event.suite.after, {}));
36
50
  recorder.add(() => event.emit(event.all.result, {}));
37
51
  recorder.add(() => codecept.teardown());
@@ -4,7 +4,7 @@ const output = require('../output');
4
4
  const event = require('../event');
5
5
  const Workers = require('../workers');
6
6
 
7
- module.exports = async function (workerCount, options) {
7
+ module.exports = async function (workerCount, selectedRuns, options) {
8
8
  process.env.profile = options.profile;
9
9
 
10
10
  const { config: testConfig, override = '' } = options;
@@ -15,6 +15,7 @@ module.exports = async function (workerCount, options) {
15
15
  by,
16
16
  testConfig,
17
17
  options,
18
+ selectedRuns,
18
19
  };
19
20
 
20
21
  const numberOfWorkers = parseInt(workerCount, 10);
package/lib/container.js CHANGED
@@ -165,7 +165,17 @@ function createHelpers(config) {
165
165
  } else {
166
166
  moduleName = `./helper/${helperName}`; // built-in helper
167
167
  }
168
- const HelperClass = require(moduleName);
168
+
169
+ // @ts-ignore
170
+ let HelperClass;
171
+ // check if the helper is the built-in, use the require() syntax.
172
+ if (moduleName.startsWith('./helper/')) {
173
+ HelperClass = require(moduleName);
174
+ } else {
175
+ // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
176
+ HelperClass = require(moduleName).default || require(moduleName);
177
+ }
178
+
169
179
  if (HelperClass._checkRequirements) {
170
180
  const requirements = HelperClass._checkRequirements();
171
181
  if (requirements) {
@@ -351,8 +361,8 @@ function loadSupportObject(modulePath, supportObjectName) {
351
361
  }
352
362
  }
353
363
  if (typeof obj !== 'function'
354
- && Object.getPrototypeOf(obj) !== Object.prototype
355
- && !Array.isArray(obj)
364
+ && Object.getPrototypeOf(obj) !== Object.prototype
365
+ && !Array.isArray(obj)
356
366
  ) {
357
367
  const methods = getObjectMethods(obj);
358
368
  Object.keys(methods)
package/lib/event.js CHANGED
@@ -91,6 +91,8 @@ module.exports = {
91
91
  bddStep: {
92
92
  before: 'bddStep.before',
93
93
  after: 'bddStep.after',
94
+ started: 'bddStep.started',
95
+ finished: 'bddStep.finished',
94
96
  },
95
97
  /**
96
98
  * @type {object}
@@ -17,6 +17,10 @@ const supportedPlatform = {
17
17
  iOS: 'iOS',
18
18
  };
19
19
 
20
+ const vendorPrefix = {
21
+ appium: 'appium',
22
+ };
23
+
20
24
  /**
21
25
  * Appium helper extends [Webriver](http://codecept.io/helpers/WebDriver/) helper.
22
26
  * It supports all browser methods and also includes special methods for mobile apps testing.
@@ -39,6 +43,7 @@ const supportedPlatform = {
39
43
  *
40
44
  * This helper should be configured in codecept.conf.ts or codecept.conf.js
41
45
  *
46
+ * * `appiumV2`: set this to true if you want to run tests with Appiumv2. See more how to setup [here](https://codecept.io/mobile/#setting-up)
42
47
  * * `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage
43
48
  * * `host`: (default: 'localhost') Appium host
44
49
  * * `port`: (default: '4723') Appium port
@@ -116,7 +121,7 @@ const supportedPlatform = {
116
121
  *
117
122
  * ## Access From Helpers
118
123
  *
119
- * Receive a Appium client from a custom helper by accessing `browser` property:
124
+ * Receive Appium client from a custom helper by accessing `browser` property:
120
125
  *
121
126
  * ```js
122
127
  * let browser = this.helpers['Appium'].browser
@@ -135,6 +140,9 @@ class Appium extends Webdriver {
135
140
  super(config);
136
141
 
137
142
  this.isRunning = false;
143
+ if (config.appiumV2 === true) {
144
+ this.appiumV2 = true;
145
+ }
138
146
  this.axios = axios.create();
139
147
 
140
148
  webdriverio = require('webdriverio');
@@ -181,14 +189,22 @@ class Appium extends Webdriver {
181
189
 
182
190
  config.baseUrl = config.url || config.baseUrl;
183
191
  if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
184
- config.capabilities = config.desiredCapabilities;
192
+ config.capabilities = this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities;
193
+ }
194
+
195
+ if (this.appiumV2) {
196
+ config.capabilities[`${vendorPrefix.appium}:deviceName`] = config[`${vendorPrefix.appium}:device`] || config.capabilities[`${vendorPrefix.appium}:deviceName`];
197
+ config.capabilities[`${vendorPrefix.appium}:browserName`] = config[`${vendorPrefix.appium}:browser`] || config.capabilities[`${vendorPrefix.appium}:browserName`];
198
+ config.capabilities[`${vendorPrefix.appium}:app`] = config[`${vendorPrefix.appium}:app`] || config.capabilities[`${vendorPrefix.appium}:app`];
199
+ config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] = config[`${vendorPrefix.appium}:tunnelIdentifier`] || config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`]; // Adding the code to connect to sauce labs via sauce tunnel
200
+ } else {
201
+ config.capabilities.deviceName = config.device || config.capabilities.deviceName;
202
+ config.capabilities.browserName = config.browser || config.capabilities.browserName;
203
+ config.capabilities.app = config.app || config.capabilities.app;
204
+ config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel
185
205
  }
186
206
 
187
- config.capabilities.deviceName = config.device || config.capabilities.deviceName;
188
- config.capabilities.browserName = config.browser || config.capabilities.browserName;
189
- config.capabilities.app = config.app || config.capabilities.app;
190
207
  config.capabilities.platformName = config.platform || config.capabilities.platformName;
191
- config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel
192
208
  config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds
193
209
 
194
210
  // [CodeceptJS compatible] transform host to hostname
@@ -203,6 +219,10 @@ class Appium extends Webdriver {
203
219
  }
204
220
 
205
221
  this.platform = null;
222
+ if (config.capabilities[`${vendorPrefix.appium}:platformName`]) {
223
+ this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase();
224
+ }
225
+
206
226
  if (config.capabilities.platformName) {
207
227
  this.platform = config.capabilities.platformName.toLowerCase();
208
228
  }
@@ -210,6 +230,18 @@ class Appium extends Webdriver {
210
230
  return config;
211
231
  }
212
232
 
233
+ _convertAppiumV2Caps(capabilities) {
234
+ const _convertedCaps = {};
235
+ for (const [key, value] of Object.entries(capabilities)) {
236
+ if (!key.startsWith(vendorPrefix.appium)) {
237
+ _convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
238
+ } else {
239
+ _convertedCaps[`${key}`] = value;
240
+ }
241
+ }
242
+ return _convertedCaps;
243
+ }
244
+
213
245
  static _config() {
214
246
  return [{
215
247
  name: 'app',
@@ -229,6 +261,11 @@ class Appium extends Webdriver {
229
261
  }
230
262
 
231
263
  async _startBrowser() {
264
+ if (this.appiumV2 === true) {
265
+ this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities);
266
+ this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities);
267
+ }
268
+
232
269
  try {
233
270
  if (this.options.multiremote) {
234
271
  this.browser = await webdriverio.multiremote(this.options.multiremote);
@@ -445,6 +482,7 @@ class Appium extends Webdriver {
445
482
  */
446
483
  async checkIfAppIsInstalled(bundleId) {
447
484
  onlyForApps.call(this, supportedPlatform.android);
485
+
448
486
  return this.browser.isAppInstalled(bundleId);
449
487
  }
450
488
 
@@ -481,7 +519,7 @@ class Appium extends Webdriver {
481
519
  async seeAppIsNotInstalled(bundleId) {
482
520
  onlyForApps.call(this, supportedPlatform.android);
483
521
  const res = await this.browser.isAppInstalled(bundleId);
484
- return truth(`app ${bundleId}`, 'to be installed').negate(res);
522
+ return truth(`app ${bundleId}`, 'not to be installed').negate(res);
485
523
  }
486
524
 
487
525
  /**
@@ -300,16 +300,16 @@ class JSONResponse extends Helper {
300
300
  *
301
301
  * I.seeResponseMatchesJsonSchema(joi => {
302
302
  * return joi.object({
303
- * name: joi.string();
304
- * id: joi.number();
303
+ * name: joi.string(),
304
+ * id: joi.number()
305
305
  * })
306
306
  * });
307
307
  *
308
308
  * // or pass a valid schema
309
- * const joi = require('joi);
309
+ * const joi = require('joi');
310
310
  *
311
311
  * I.seeResponseMatchesJsonSchema(joi.object({
312
- * name: joi.string();
312
+ * name: joi.string(),
313
313
  * id: joi.number();
314
314
  * });
315
315
  * ```
@@ -694,7 +694,7 @@ class Nightmare extends Helper {
694
694
  async appendField(field, value) {
695
695
  const el = await findField.call(this, field);
696
696
  assertElementExists(el, field, 'Field');
697
- return this.browser.enterText(el, value, false)
697
+ return this.browser.enterText(el, value.toString(), false)
698
698
  .wait(this.options.waitForAction);
699
699
  }
700
700
 
@@ -0,0 +1,122 @@
1
+ const Helper = require('@codeceptjs/helper');
2
+ const AiAssistant = require('../ai');
3
+ const standardActingHelpers = require('../plugin/standardActingHelpers');
4
+ const Container = require('../container');
5
+ const { splitByChunks, minifyHtml } = require('../html');
6
+
7
+ /**
8
+ * OpenAI Helper for CodeceptJS.
9
+ *
10
+ * This helper class provides integration with the OpenAI 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.
12
+ *
13
+ * ## Configuration
14
+ *
15
+ * This helper should be configured in codecept.json or codecept.conf.js
16
+ *
17
+ * * `chunkSize`: (optional, default: 80000) - The maximum number of characters to send to the OpenAI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4.
18
+ */
19
+ class OpenAI extends Helper {
20
+ constructor(config) {
21
+ super(config);
22
+ this.aiAssistant = new AiAssistant();
23
+
24
+ this.options = {
25
+ chunkSize: 80000,
26
+ };
27
+ this.options = { ...this.options, ...config };
28
+
29
+ const helpers = Container.helpers();
30
+
31
+ for (const helperName of standardActingHelpers) {
32
+ if (Object.keys(helpers).indexOf(helperName) > -1) {
33
+ this.helper = helpers[helperName];
34
+ break;
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Asks the OpenAI GPT language model a question based on the provided prompt within the context of the current page's HTML.
41
+ *
42
+ * ```js
43
+ * I.askGptOnPage('what does this page do?');
44
+ * ```
45
+ *
46
+ * @async
47
+ * @param {string} prompt - The question or prompt to ask the GPT model.
48
+ * @returns {Promise<string>} - A Promise that resolves to the generated responses from the GPT model, joined by newlines.
49
+ */
50
+ async askGptOnPage(prompt) {
51
+ const html = await this.helper.grabSource();
52
+
53
+ const htmlChunks = splitByChunks(html, this.options.chunkSize);
54
+
55
+ if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`);
56
+
57
+ const responses = [];
58
+
59
+ for (const chunk of htmlChunks) {
60
+ const messages = [
61
+ { role: 'user', content: prompt },
62
+ { role: 'user', content: `Within this HTML: ${minifyHtml(chunk)}` },
63
+ ];
64
+
65
+ 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' });
66
+
67
+ const response = await this.aiAssistant.createCompletion(messages);
68
+
69
+ console.log(response);
70
+
71
+ responses.push(response);
72
+ }
73
+
74
+ return responses.join('\n\n');
75
+ }
76
+
77
+ /**
78
+ * Asks the OpenAI GPT-3.5 language model a question based on the provided prompt within the context of a specific HTML fragment on the current page.
79
+ *
80
+ * ```js
81
+ * I.askGptOnPageFragment('describe features of this screen', '.screen');
82
+ * ```
83
+ *
84
+ * @async
85
+ * @param {string} prompt - The question or prompt to ask the GPT-3.5 model.
86
+ * @param {string} locator - The locator or selector used to identify the HTML fragment on the page.
87
+ * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
88
+ */
89
+ async askGptOnPageFragment(prompt, locator) {
90
+ const html = await this.helper.grabHTMLFrom(locator);
91
+
92
+ const messages = [
93
+ { role: 'user', content: prompt },
94
+ { role: 'user', content: `Within this HTML: ${minifyHtml(html)}` },
95
+ ];
96
+
97
+ const response = await this.aiAssistant.createCompletion(messages);
98
+
99
+ console.log(response);
100
+
101
+ return response;
102
+ }
103
+
104
+ /**
105
+ * Send a general request to ChatGPT and return response.
106
+ * @param {string} prompt
107
+ * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
108
+ */
109
+ async askGptGeneralPrompt(prompt) {
110
+ const messages = [
111
+ { role: 'user', content: prompt },
112
+ ];
113
+
114
+ const completion = await this.aiAssistant.createCompletion(messages);
115
+
116
+ const response = completion?.data?.choices[0]?.message?.content;
117
+
118
+ console.log(response);
119
+
120
+ return response;
121
+ }
122
+ }