codeceptjs 3.6.4-beta.2 → 3.6.5-beta.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 (92) hide show
  1. package/bin/codecept.js +84 -63
  2. package/lib/ai.js +47 -1
  3. package/lib/assert/empty.js +19 -19
  4. package/lib/assert/equal.js +32 -30
  5. package/lib/assert/error.js +14 -14
  6. package/lib/assert/include.js +42 -42
  7. package/lib/assert/throws.js +13 -11
  8. package/lib/assert/truth.js +17 -18
  9. package/lib/command/configMigrate.js +57 -52
  10. package/lib/command/definitions.js +88 -88
  11. package/lib/command/dryRun.js +65 -63
  12. package/lib/command/generate.js +191 -181
  13. package/lib/command/info.js +39 -37
  14. package/lib/command/init.js +289 -286
  15. package/lib/command/interactive.js +32 -32
  16. package/lib/command/list.js +26 -26
  17. package/lib/command/run-multiple.js +113 -93
  18. package/lib/command/run-rerun.js +22 -22
  19. package/lib/command/run-workers.js +63 -63
  20. package/lib/command/run.js +24 -26
  21. package/lib/command/utils.js +64 -63
  22. package/lib/data/context.js +60 -60
  23. package/lib/data/dataScenarioConfig.js +47 -47
  24. package/lib/data/dataTableArgument.js +29 -29
  25. package/lib/data/table.js +26 -20
  26. package/lib/helper/AI.js +114 -35
  27. package/lib/helper/ApiDataFactory.js +72 -69
  28. package/lib/helper/Appium.js +409 -379
  29. package/lib/helper/ExpectHelper.js +214 -248
  30. package/lib/helper/FileSystem.js +77 -78
  31. package/lib/helper/GraphQL.js +44 -43
  32. package/lib/helper/GraphQLDataFactory.js +49 -50
  33. package/lib/helper/JSONResponse.js +64 -62
  34. package/lib/helper/Mochawesome.js +28 -28
  35. package/lib/helper/MockServer.js +12 -12
  36. package/lib/helper/Nightmare.js +664 -572
  37. package/lib/helper/Playwright.js +1320 -1211
  38. package/lib/helper/Protractor.js +663 -629
  39. package/lib/helper/Puppeteer.js +1232 -1124
  40. package/lib/helper/REST.js +115 -69
  41. package/lib/helper/TestCafe.js +490 -491
  42. package/lib/helper/WebDriver.js +1294 -1156
  43. package/lib/history.js +16 -3
  44. package/lib/interfaces/bdd.js +38 -51
  45. package/lib/interfaces/featureConfig.js +19 -19
  46. package/lib/interfaces/gherkin.js +122 -111
  47. package/lib/interfaces/scenarioConfig.js +29 -29
  48. package/lib/listener/artifacts.js +9 -9
  49. package/lib/listener/config.js +24 -23
  50. package/lib/listener/exit.js +12 -12
  51. package/lib/listener/helpers.js +42 -42
  52. package/lib/listener/mocha.js +11 -11
  53. package/lib/listener/retry.js +32 -30
  54. package/lib/listener/steps.js +50 -51
  55. package/lib/listener/timeout.js +53 -53
  56. package/lib/pause.js +17 -3
  57. package/lib/plugin/allure.js +14 -14
  58. package/lib/plugin/autoDelay.js +29 -36
  59. package/lib/plugin/autoLogin.js +70 -66
  60. package/lib/plugin/commentStep.js +18 -18
  61. package/lib/plugin/coverage.js +92 -77
  62. package/lib/plugin/customLocator.js +20 -19
  63. package/lib/plugin/debugErrors.js +24 -24
  64. package/lib/plugin/eachElement.js +37 -37
  65. package/lib/plugin/fakerTransform.js +6 -6
  66. package/lib/plugin/heal.js +66 -63
  67. package/lib/plugin/pauseOnFail.js +10 -10
  68. package/lib/plugin/retryFailedStep.js +31 -38
  69. package/lib/plugin/retryTo.js +28 -28
  70. package/lib/plugin/screenshotOnFail.js +107 -86
  71. package/lib/plugin/selenoid.js +131 -117
  72. package/lib/plugin/standardActingHelpers.js +2 -8
  73. package/lib/plugin/stepByStepReport.js +102 -92
  74. package/lib/plugin/stepTimeout.js +23 -22
  75. package/lib/plugin/subtitles.js +34 -34
  76. package/lib/plugin/tryTo.js +39 -29
  77. package/lib/plugin/wdio.js +77 -72
  78. package/lib/template/heal.js +11 -14
  79. package/package.json +5 -3
  80. package/translations/de-DE.js +1 -1
  81. package/translations/fr-FR.js +1 -1
  82. package/translations/index.js +9 -9
  83. package/translations/it-IT.js +1 -1
  84. package/translations/ja-JP.js +1 -1
  85. package/translations/pl-PL.js +1 -1
  86. package/translations/pt-BR.js +1 -1
  87. package/translations/ru-RU.js +1 -1
  88. package/translations/zh-CN.js +1 -1
  89. package/translations/zh-TW.js +1 -1
  90. package/typings/index.d.ts +42 -19
  91. package/typings/promiseBasedTypes.d.ts +280 -1
  92. package/typings/types.d.ts +76 -1
@@ -1,84 +1,84 @@
1
1
  class DataScenarioConfig {
2
2
  constructor(scenarios) {
3
- this.scenarios = scenarios;
3
+ this.scenarios = scenarios
4
4
  }
5
5
 
6
6
  /**
7
- * Declares that test throws error.
8
- * Can pass an Error object or regex matching expected message.
9
- *
10
- * @param {*} err
11
- */
7
+ * Declares that test throws error.
8
+ * Can pass an Error object or regex matching expected message.
9
+ *
10
+ * @param {*} err
11
+ */
12
12
  throws(err) {
13
- this.scenarios.forEach(scenario => scenario.throws(err));
14
- return this;
13
+ this.scenarios.forEach((scenario) => scenario.throws(err))
14
+ return this
15
15
  }
16
16
 
17
17
  /**
18
- * Declares that test should fail.
19
- * If test passes - throws an error.
20
- * Can pass an Error object or regex matching expected message.
21
- *
22
- */
18
+ * Declares that test should fail.
19
+ * If test passes - throws an error.
20
+ * Can pass an Error object or regex matching expected message.
21
+ *
22
+ */
23
23
  fails() {
24
- this.scenarios.forEach(scenario => scenario.fails());
25
- return this;
24
+ this.scenarios.forEach((scenario) => scenario.fails())
25
+ return this
26
26
  }
27
27
 
28
28
  /**
29
- * Retry this test for x times
30
- *
31
- * @param {*} retries
32
- */
29
+ * Retry this test for x times
30
+ *
31
+ * @param {*} retries
32
+ */
33
33
  retry(retries) {
34
- this.scenarios.forEach(scenario => scenario.retry(retries));
35
- return this;
34
+ this.scenarios.forEach((scenario) => scenario.retry(retries))
35
+ return this
36
36
  }
37
37
 
38
38
  /**
39
- * Set timeout for this test
40
- * @param {*} timeout
41
- */
39
+ * Set timeout for this test
40
+ * @param {*} timeout
41
+ */
42
42
  timeout(timeout) {
43
- this.scenarios.forEach(scenario => scenario.timeout(timeout));
44
- return this;
43
+ this.scenarios.forEach((scenario) => scenario.timeout(timeout))
44
+ return this
45
45
  }
46
46
 
47
47
  /**
48
- * Configures a helper.
49
- * Helper name can be omitted and values will be applied to first helper.
50
- */
48
+ * Configures a helper.
49
+ * Helper name can be omitted and values will be applied to first helper.
50
+ */
51
51
  config(helper, obj) {
52
- this.scenarios.forEach(scenario => scenario.config(helper, obj));
53
- return this;
52
+ this.scenarios.forEach((scenario) => scenario.config(helper, obj))
53
+ return this
54
54
  }
55
55
 
56
56
  /**
57
- * Append a tag name to scenario title
58
- * @param {*} tagName
59
- */
57
+ * Append a tag name to scenario title
58
+ * @param {*} tagName
59
+ */
60
60
  tag(tagName) {
61
- this.scenarios.forEach(scenario => scenario.tag(tagName));
62
- return this;
61
+ this.scenarios.forEach((scenario) => scenario.tag(tagName))
62
+ return this
63
63
  }
64
64
 
65
65
  /**
66
- * Pass in additional objects to inject into test
67
- * @param {*} obj
68
- */
66
+ * Pass in additional objects to inject into test
67
+ * @param {*} obj
68
+ */
69
69
  inject(obj) {
70
- this.scenarios.forEach(scenario => scenario.inject(obj));
71
- return this;
70
+ this.scenarios.forEach((scenario) => scenario.inject(obj))
71
+ return this
72
72
  }
73
73
 
74
74
  /**
75
- * Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection
76
- * @param {*} dependencies
77
- */
75
+ * Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection
76
+ * @param {*} dependencies
77
+ */
78
78
  injectDependencies(dependencies) {
79
- this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies));
80
- return this;
79
+ this.scenarios.forEach((scenario) => scenario.injectDependencies(dependencies))
80
+ return this
81
81
  }
82
82
  }
83
83
 
84
- module.exports = DataScenarioConfig;
84
+ module.exports = DataScenarioConfig
@@ -7,60 +7,60 @@ class DataTableArgument {
7
7
  constructor(gherkinDataTable) {
8
8
  this.rawData = gherkinDataTable.rows.map((row) => {
9
9
  return row.cells.map((cell) => {
10
- return cell.value;
11
- });
12
- });
10
+ return cell.value
11
+ })
12
+ })
13
13
  }
14
14
 
15
15
  /** Returns the table as a 2-D array
16
- * @returns {string[][]}
17
- */
16
+ * @returns {string[][]}
17
+ */
18
18
  raw() {
19
- return this.rawData.slice(0);
19
+ return this.rawData.slice(0)
20
20
  }
21
21
 
22
22
  /** Returns the table as a 2-D array, without the first row
23
- * @returns {string[][]}
24
- */
23
+ * @returns {string[][]}
24
+ */
25
25
  rows() {
26
- const copy = this.raw();
27
- copy.shift();
28
- return copy;
26
+ const copy = this.raw()
27
+ copy.shift()
28
+ return copy
29
29
  }
30
30
 
31
31
  /** Returns an array of objects where each row is converted to an object (column header is the key)
32
- * @returns {any[]}
33
- */
32
+ * @returns {any[]}
33
+ */
34
34
  hashes() {
35
- const copy = this.raw();
36
- const header = copy.shift();
35
+ const copy = this.raw()
36
+ const header = copy.shift()
37
37
  return copy.map((row) => {
38
- const r = {};
39
- row.forEach((cell, index) => r[header[index]] = cell);
40
- return r;
41
- });
38
+ const r = {}
39
+ row.forEach((cell, index) => (r[header[index]] = cell))
40
+ return r
41
+ })
42
42
  }
43
43
 
44
44
  /** Returns an object where each row corresponds to an entry
45
45
  * (first column is the key, second column is the value)
46
- * @returns {Record<string, string>}
47
- */
46
+ * @returns {Record<string, string>}
47
+ */
48
48
  rowsHash() {
49
- const rows = this.raw();
50
- const everyRowHasTwoColumns = rows.every((row) => row.length === 2);
49
+ const rows = this.raw()
50
+ const everyRowHasTwoColumns = rows.every((row) => row.length === 2)
51
51
  if (!everyRowHasTwoColumns) {
52
- throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns');
52
+ throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns')
53
53
  }
54
54
  /** @type {Record<string, string>} */
55
- const result = {};
56
- rows.forEach((x) => (result[x[0]] = x[1]));
57
- return result;
55
+ const result = {}
56
+ rows.forEach((x) => (result[x[0]] = x[1]))
57
+ return result
58
58
  }
59
59
 
60
60
  /** Transposed the data */
61
61
  transpose() {
62
- this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]));
62
+ this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]))
63
63
  }
64
64
  }
65
65
 
66
- module.exports = DataTableArgument;
66
+ module.exports = DataTableArgument
package/lib/data/table.js CHANGED
@@ -4,40 +4,46 @@
4
4
  class DataTable {
5
5
  /** @param {Array<*>} array */
6
6
  constructor(array) {
7
- this.array = array;
8
- this.rows = new Array(0);
7
+ this.array = array
8
+ this.rows = new Array(0)
9
9
  }
10
10
 
11
11
  /** @param {Array<*>} array */
12
12
  add(array) {
13
- if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`);
14
- const tempObj = {};
15
- let arrayCounter = 0;
13
+ if (array.length !== this.array.length)
14
+ throw new Error(
15
+ `There is too many elements in given data array. Please provide data in this format: ${this.array}`,
16
+ )
17
+ const tempObj = {}
18
+ let arrayCounter = 0
16
19
  this.array.forEach((elem) => {
17
- tempObj[elem] = array[arrayCounter];
18
- tempObj.toString = () => JSON.stringify(tempObj);
19
- arrayCounter++;
20
- });
21
- this.rows.push({ skip: false, data: tempObj });
20
+ tempObj[elem] = array[arrayCounter]
21
+ tempObj.toString = () => JSON.stringify(tempObj)
22
+ arrayCounter++
23
+ })
24
+ this.rows.push({ skip: false, data: tempObj })
22
25
  }
23
26
 
24
27
  /** @param {Array<*>} array */
25
28
  xadd(array) {
26
- if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`);
27
- const tempObj = {};
28
- let arrayCounter = 0;
29
+ if (array.length !== this.array.length)
30
+ throw new Error(
31
+ `There is too many elements in given data array. Please provide data in this format: ${this.array}`,
32
+ )
33
+ const tempObj = {}
34
+ let arrayCounter = 0
29
35
  this.array.forEach((elem) => {
30
- tempObj[elem] = array[arrayCounter];
31
- tempObj.toString = () => JSON.stringify(tempObj);
32
- arrayCounter++;
33
- });
34
- this.rows.push({ skip: true, data: tempObj });
36
+ tempObj[elem] = array[arrayCounter]
37
+ tempObj.toString = () => JSON.stringify(tempObj)
38
+ arrayCounter++
39
+ })
40
+ this.rows.push({ skip: true, data: tempObj })
35
41
  }
36
42
 
37
43
  /** @param {Function} func */
38
44
  filter(func) {
39
- return this.rows.filter(row => func(row.data));
45
+ return this.rows.filter((row) => func(row.data))
40
46
  }
41
47
  }
42
48
 
43
- module.exports = DataTable;
49
+ module.exports = DataTable
package/lib/helper/AI.js CHANGED
@@ -1,8 +1,14 @@
1
- const Helper = require('@codeceptjs/helper');
2
- const ai = require('../ai');
3
- const standardActingHelpers = require('../plugin/standardActingHelpers');
4
- const Container = require('../container');
5
- const { splitByChunks, minifyHtml } = require('../html');
1
+ const Helper = require('@codeceptjs/helper')
2
+ const ora = require('ora-classic')
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const ai = require('../ai')
6
+ const standardActingHelpers = require('../plugin/standardActingHelpers')
7
+ const Container = require('../container')
8
+ const { splitByChunks, minifyHtml } = require('../html')
9
+ const { beautify } = require('../utils')
10
+ const output = require('../output')
11
+ const { registerVariable } = require('../pause')
6
12
 
7
13
  /**
8
14
  * AI Helper for CodeceptJS.
@@ -10,6 +16,8 @@ const { splitByChunks, minifyHtml } = require('../html');
10
16
  * 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
17
  * This helper should be enabled with any web helpers like Playwright or Puppeteer or WebDrvier to ensure the HTML context is available.
12
18
  *
19
+ * Use it only in development mode. It is recommended to run it only inside pause() mode.
20
+ *
13
21
  * ## Configuration
14
22
  *
15
23
  * This helper should be configured in codecept.json or codecept.conf.js
@@ -18,22 +26,22 @@ const { splitByChunks, minifyHtml } = require('../html');
18
26
  */
19
27
  class AI extends Helper {
20
28
  constructor(config) {
21
- super(config);
22
- this.aiAssistant = ai;
29
+ super(config)
30
+ this.aiAssistant = ai
23
31
 
24
32
  this.options = {
25
33
  chunkSize: 80000,
26
- };
27
- this.options = { ...this.options, ...config };
34
+ }
35
+ this.options = { ...this.options, ...config }
28
36
  }
29
37
 
30
38
  _beforeSuite() {
31
- const helpers = Container.helpers();
39
+ const helpers = Container.helpers()
32
40
 
33
41
  for (const helperName of standardActingHelpers) {
34
42
  if (Object.keys(helpers).indexOf(helperName) > -1) {
35
- this.helper = helpers[helperName];
36
- break;
43
+ this.helper = helpers[helperName]
44
+ break
37
45
  }
38
46
  }
39
47
  }
@@ -50,30 +58,34 @@ class AI extends Helper {
50
58
  * @returns {Promise<string>} - A Promise that resolves to the generated responses from the GPT model, joined by newlines.
51
59
  */
52
60
  async askGptOnPage(prompt) {
53
- const html = await this.helper.grabSource();
61
+ const html = await this.helper.grabSource()
54
62
 
55
- const htmlChunks = splitByChunks(html, this.options.chunkSize);
63
+ const htmlChunks = splitByChunks(html, this.options.chunkSize)
56
64
 
57
- if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`);
65
+ if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`)
58
66
 
59
- const responses = [];
67
+ const responses = []
60
68
 
61
69
  for (const chunk of htmlChunks) {
62
70
  const messages = [
63
71
  { role: 'user', content: prompt },
64
72
  { role: 'user', content: `Within this HTML: ${minifyHtml(chunk)}` },
65
- ];
73
+ ]
66
74
 
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' });
75
+ if (htmlChunks.length > 1)
76
+ messages.push({
77
+ role: 'user',
78
+ content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment',
79
+ })
68
80
 
69
- const response = await this.aiAssistant.createCompletion(messages);
81
+ const response = await this._processAIRequest(messages)
70
82
 
71
- console.log(response);
83
+ output.print(response)
72
84
 
73
- responses.push(response);
85
+ responses.push(response)
74
86
  }
75
87
 
76
- return responses.join('\n\n');
88
+ return responses.join('\n\n')
77
89
  }
78
90
 
79
91
  /**
@@ -89,36 +101,103 @@ class AI extends Helper {
89
101
  * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
90
102
  */
91
103
  async askGptOnPageFragment(prompt, locator) {
92
- const html = await this.helper.grabHTMLFrom(locator);
104
+ const html = await this.helper.grabHTMLFrom(locator)
93
105
 
94
106
  const messages = [
95
107
  { role: 'user', content: prompt },
96
108
  { role: 'user', content: `Within this HTML: ${minifyHtml(html)}` },
97
- ];
109
+ ]
98
110
 
99
- const response = await this.aiAssistant.createCompletion(messages);
111
+ const response = await this._processAIRequest(messages)
100
112
 
101
- console.log(response);
113
+ output.print(response)
102
114
 
103
- return response;
115
+ return response
104
116
  }
105
117
 
106
118
  /**
107
- * Send a general request to ChatGPT and return response.
119
+ * Send a general request to AI and return response.
108
120
  * @param {string} prompt
109
121
  * @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
110
122
  */
111
123
  async askGptGeneralPrompt(prompt) {
112
- const messages = [
113
- { role: 'user', content: prompt },
114
- ];
124
+ const messages = [{ role: 'user', content: prompt }]
115
125
 
116
- const response = await this.aiAssistant.createCompletion(messages);
126
+ const response = await this._processAIRequest(messages)
117
127
 
118
- console.log(response);
128
+ output.print(response)
129
+
130
+ return response
131
+ }
132
+
133
+ /**
134
+ * Generates PageObject for current page using AI.
135
+ *
136
+ * It saves the PageObject to the output directory. You can review the page object and adjust it as needed and move to pages directory.
137
+ * Prompt can be customized in a global config file.
138
+ *
139
+ * ```js
140
+ * // create page object for whole page
141
+ * I.askForPageObject('home');
142
+ *
143
+ * // create page object with extra prompt
144
+ * I.askForPageObject('home', 'implement signIn(username, password) method');
145
+ *
146
+ * // create page object for a specific element
147
+ * I.askForPageObject('home', null, '.detail');
148
+ * ```
149
+ *
150
+ * Asks for a page object based on the provided page name, locator, and extra prompt.
151
+ *
152
+ * @async
153
+ * @param {string} pageName - The name of the page to retrieve the object for.
154
+ * @param {string|null} [extraPrompt=null] - An optional extra prompt for additional context or information.
155
+ * @param {string|null} [locator=null] - An optional locator to find a specific element on the page.
156
+ * @returns {Promise<Object>} A promise that resolves to the requested page object.
157
+ */
158
+ async askForPageObject(pageName, extraPrompt = null, locator = null) {
159
+ const html = locator ? await this.helper.grabHTMLFrom(locator) : await this.helper.grabSource()
160
+
161
+ const spinner = ora(' Processing AI request...').start()
162
+ await this.aiAssistant.setHtmlContext(html)
163
+ const response = await this.aiAssistant.generatePageObject(extraPrompt, locator)
164
+ spinner.stop()
165
+
166
+ if (!response[0]) {
167
+ output.error('No response from AI')
168
+ return ''
169
+ }
170
+
171
+ const code = beautify(response[0])
172
+
173
+ output.print('----- Generated PageObject ----')
174
+ output.print(code)
175
+ output.print('-------------------------------')
176
+
177
+ const fileName = path.join(output_dir, `${pageName}Page-${Date.now()}.js`)
178
+
179
+ output.print(output.styles.bold(`Page object for ${pageName} is saved to ${output.styles.bold(fileName)}`))
180
+ fs.writeFileSync(fileName, code)
181
+
182
+ try {
183
+ registerVariable('page', require(fileName))
184
+ output.success('Page object registered for this session as `page` variable')
185
+ output.print('Use `=>page.methodName()` in shell to run methods of page object')
186
+ output.print('Use `click(page.locatorName)` to check locators of page object')
187
+ } catch (err) {
188
+ output.error('Error while registering page object')
189
+ output.error(err.message)
190
+ }
191
+
192
+ return code
193
+ }
119
194
 
120
- return response;
195
+ async _processAIRequest(messages) {
196
+ const spinner = ora(' Processing AI request...').start()
197
+ const response = await this.aiAssistant.createCompletion(messages)
198
+ spinner.stop()
199
+ return response
121
200
  }
122
201
  }
123
202
 
124
- module.exports = AI;
203
+ module.exports = AI