codeceptjs 3.7.0-beta.5 → 3.7.0-beta.6

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.
package/lib/effects.js CHANGED
@@ -1,84 +1,33 @@
1
1
  const recorder = require('./recorder')
2
2
  const { debug } = require('./output')
3
3
  const store = require('./store')
4
+ const event = require('./event')
4
5
 
5
6
  /**
6
- * @module hopeThat
7
- *
8
- * `hopeThat` is a utility function for CodeceptJS tests that allows for soft assertions.
9
- * It enables conditional assertions without terminating the test upon failure.
10
- * This is particularly useful in scenarios like A/B testing, handling unexpected elements,
11
- * or performing multiple assertions where you want to collect all results before deciding
12
- * on the test outcome.
13
- *
14
- * ## Use Cases
15
- *
16
- * - **Multiple Conditional Assertions**: Perform several assertions and evaluate all their outcomes together.
17
- * - **A/B Testing**: Handle different variants in A/B tests without failing the entire test upon one variant's failure.
18
- * - **Unexpected Elements**: Manage elements that may or may not appear, such as "Accept Cookie" banners.
19
- *
20
- * ## Examples
21
- *
22
- * ### Multiple Conditional Assertions
23
- *
24
- * Add the assertion library:
25
- * ```js
26
- * const assert = require('assert');
27
- * const { hopeThat } = require('codeceptjs/effects');
28
- * ```
29
- *
30
- * Use `hopeThat` with assertions:
31
- * ```js
32
- * const result1 = await hopeThat(() => I.see('Hello, user'));
33
- * const result2 = await hopeThat(() => I.seeElement('.welcome'));
34
- * assert.ok(result1 && result2, 'Assertions were not successful');
35
- * ```
36
- *
37
- * ### Optional Click
38
- *
39
- * ```js
40
- * const { hopeThat } = require('codeceptjs/effects');
41
- *
42
- * I.amOnPage('/');
43
- * await hopeThat(() => I.click('Agree', '.cookies'));
44
- * ```
45
- *
46
- * Performs a soft assertion within CodeceptJS tests.
47
- *
48
- * This function records the execution of a callback containing assertion logic.
49
- * If the assertion fails, it logs the failure without stopping the test execution.
50
- * It is useful for scenarios where multiple assertions are performed, and you want
51
- * to evaluate all outcomes before deciding on the test result.
52
- *
53
- * ## Usage
54
- *
55
- * ```js
56
- * const result = await hopeThat(() => I.see('Welcome'));
57
- *
58
- * // If the text "Welcome" is on the page, result => true
59
- * // If the text "Welcome" is not on the page, result => false
60
- * ```
7
+ * A utility function for CodeceptJS tests that acts as a soft assertion.
8
+ * Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately.
61
9
  *
62
10
  * @async
63
11
  * @function hopeThat
64
- * @param {Function} callback - The callback function containing the soft assertion logic.
65
- * @returns {Promise<boolean | any>} - Resolves to `true` if the assertion is successful, or `false` if it fails.
12
+ * @param {Function} callback - The callback function containing the logic to validate.
13
+ * This function should perform the desired assertion or condition check.
14
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the assertion or condition was successful,
15
+ * or `false` if an error occurred.
16
+ *
17
+ * @description
18
+ * - Designed for use in CodeceptJS tests as a "soft assertion."
19
+ * Unlike standard assertions, it does not stop the test execution on failure.
20
+ * - Starts a new recorder session named 'hopeThat' and manages state restoration.
21
+ * - Logs errors and attaches them as notes to the test, enabling post-test reporting of soft assertion failures.
22
+ * - Resets the `store.hopeThat` flag after the execution, ensuring clean state for subsequent operations.
66
23
  *
67
24
  * @example
68
- * // Multiple Conditional Assertions
69
- * const assert = require('assert');
70
- * const { hopeThat } = require('codeceptjs/effects');
25
+ * const { hopeThat } = require('codeceptjs/effects')
26
+ * await hopeThat(() => {
27
+ * I.see('Welcome'); // Perform a soft assertion
28
+ * });
71
29
  *
72
- * const result1 = await hopeThat(() => I.see('Hello, user'));
73
- * const result2 = await hopeThat(() => I.seeElement('.welcome'));
74
- * assert.ok(result1 && result2, 'Assertions were not successful');
75
- *
76
- * @example
77
- * // Optional Click
78
- * const { hopeThat } = require('codeceptjs/effects');
79
- *
80
- * I.amOnPage('/');
81
- * await hopeThat(() => I.click('Agree', '.cookies'));
30
+ * @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test.
82
31
  */
83
32
  async function hopeThat(callback) {
84
33
  if (store.dryRun) return
@@ -100,6 +49,9 @@ async function hopeThat(callback) {
100
49
  result = false
101
50
  const msg = err.inspect ? err.inspect() : err.toString()
102
51
  debug(`Unsuccessful assertion > ${msg}`)
52
+ event.dispatcher.once(event.test.finished, test => {
53
+ test.notes.push({ type: 'conditionalError', text: msg })
54
+ })
103
55
  recorder.session.restore(sessionName)
104
56
  return result
105
57
  })
@@ -118,6 +70,149 @@ async function hopeThat(callback) {
118
70
  )
119
71
  }
120
72
 
73
+ /**
74
+ * A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
75
+ *
76
+ * @async
77
+ * @function retryTo
78
+ * @param {Function} callback - The function to execute, which will be retried upon failure.
79
+ * Receives the current retry count as an argument.
80
+ * @param {number} maxTries - The maximum number of attempts to retry the callback.
81
+ * @param {number} [pollInterval=200] - The delay (in milliseconds) between retry attempts.
82
+ * @returns {Promise<void|any>} A promise that resolves when the callback executes successfully, or rejects after reaching the maximum retries.
83
+ *
84
+ * @description
85
+ * - This function is designed for use in CodeceptJS tests to handle intermittent or flaky test steps.
86
+ * - Starts a new recorder session for each retry attempt, ensuring proper state management and error handling.
87
+ * - Logs errors and retries the callback until it either succeeds or the maximum number of attempts is reached.
88
+ * - Restores the session state after each attempt, whether successful or not.
89
+ *
90
+ * @example
91
+ * const { hopeThat } = require('codeceptjs/effects')
92
+ * await retryTo((tries) => {
93
+ * if (tries < 3) {
94
+ * I.see('Non-existent element'); // Simulates a failure
95
+ * } else {
96
+ * I.see('Welcome'); // Succeeds on the 3rd attempt
97
+ * }
98
+ * }, 5, 300); // Retry up to 5 times, with a 300ms interval
99
+ *
100
+ * @throws Will reject with the last error encountered if the maximum retries are exceeded.
101
+ */
102
+ async function retryTo(callback, maxTries, pollInterval = 200) {
103
+ const sessionName = 'retryTo'
104
+
105
+ return new Promise((done, reject) => {
106
+ let tries = 1
107
+
108
+ function handleRetryException(err) {
109
+ recorder.throw(err)
110
+ reject(err)
111
+ }
112
+
113
+ const tryBlock = async () => {
114
+ tries++
115
+ recorder.session.start(`${sessionName} ${tries}`)
116
+ try {
117
+ await callback(tries)
118
+ } catch (err) {
119
+ handleRetryException(err)
120
+ }
121
+
122
+ // Call done if no errors
123
+ recorder.add(() => {
124
+ recorder.session.restore(`${sessionName} ${tries}`)
125
+ done(null)
126
+ })
127
+
128
+ // Catch errors and retry
129
+ recorder.session.catch(err => {
130
+ recorder.session.restore(`${sessionName} ${tries}`)
131
+ if (tries <= maxTries) {
132
+ debug(`Error ${err}... Retrying`)
133
+ recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval))
134
+ } else {
135
+ // if maxTries reached
136
+ handleRetryException(err)
137
+ }
138
+ })
139
+ }
140
+
141
+ recorder.add(sessionName, tryBlock).catch(err => {
142
+ console.error('An error occurred:', err)
143
+ done(null)
144
+ })
145
+ })
146
+ }
147
+
148
+ /**
149
+ * A CodeceptJS utility function to attempt a step or callback without failing the test.
150
+ * If the step fails, the test continues execution without interruption, and the result is logged.
151
+ *
152
+ * @async
153
+ * @function tryTo
154
+ * @param {Function} callback - The function to execute, which may succeed or fail.
155
+ * This function contains the logic to be attempted.
156
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the step succeeds, or `false` if it fails.
157
+ *
158
+ * @description
159
+ * - Useful for scenarios where certain steps are optional or their failure should not interrupt the test flow.
160
+ * - Starts a new recorder session named 'tryTo' for isolation and error handling.
161
+ * - Captures errors during execution and logs them for debugging purposes.
162
+ * - Ensures the `store.tryTo` flag is reset after execution to maintain a clean state.
163
+ *
164
+ * @example
165
+ * const { tryTo } = require('codeceptjs/effects')
166
+ * const wasSuccessful = await tryTo(() => {
167
+ * I.see('Welcome'); // Attempt to find an element on the page
168
+ * });
169
+ *
170
+ * if (!wasSuccessful) {
171
+ * I.say('Optional step failed, but test continues.');
172
+ * }
173
+ *
174
+ * @throws Will handle errors internally, logging them and returning `false` as the result.
175
+ */
176
+ async function tryTo(callback) {
177
+ if (store.dryRun) return
178
+ const sessionName = 'tryTo'
179
+
180
+ let result = false
181
+ return recorder.add(
182
+ sessionName,
183
+ () => {
184
+ recorder.session.start(sessionName)
185
+ store.tryTo = true
186
+ callback()
187
+ recorder.add(() => {
188
+ result = true
189
+ recorder.session.restore(sessionName)
190
+ return result
191
+ })
192
+ recorder.session.catch(err => {
193
+ result = false
194
+ const msg = err.inspect ? err.inspect() : err.toString()
195
+ debug(`Unsuccessful try > ${msg}`)
196
+ recorder.session.restore(sessionName)
197
+ return result
198
+ })
199
+ return recorder.add(
200
+ 'result',
201
+ () => {
202
+ store.tryTo = undefined
203
+ return result
204
+ },
205
+ true,
206
+ false,
207
+ )
208
+ },
209
+ false,
210
+ false,
211
+ )
212
+ }
213
+
121
214
  module.exports = {
122
215
  hopeThat,
216
+ retryTo,
217
+ tryTo,
123
218
  }
@@ -1,127 +1,19 @@
1
- const recorder = require('../recorder')
2
- const { debug } = require('../output')
3
-
4
- const defaultConfig = {
5
- registerGlobal: true,
6
- pollInterval: 200,
7
- }
8
-
9
- /**
10
- *
11
- *
12
- * Adds global `retryTo` which retries steps a few times before failing.
13
- *
14
- * Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
15
- *
16
- * ```js
17
- * plugins: {
18
- * retryTo: {
19
- * enabled: true
20
- * }
21
- * }
22
- * ```
23
- *
24
- * Use it in your tests:
25
- *
26
- * ```js
27
- * // retry these steps 5 times before failing
28
- * await retryTo((tryNum) => {
29
- * I.switchTo('#editor frame');
30
- * I.click('Open');
31
- * I.see('Opened')
32
- * }, 5);
33
- * ```
34
- * Set polling interval as 3rd argument (200ms by default):
35
- *
36
- * ```js
37
- * // retry these steps 5 times before failing
38
- * await retryTo((tryNum) => {
39
- * I.switchTo('#editor frame');
40
- * I.click('Open');
41
- * I.see('Opened')
42
- * }, 5, 100);
43
- * ```
44
- *
45
- * Default polling interval can be changed in a config:
46
- *
47
- * ```js
48
- * plugins: {
49
- * retryTo: {
50
- * enabled: true,
51
- * pollInterval: 500,
52
- * }
53
- * }
54
- * ```
55
- *
56
- * Disables retryFailedStep plugin for steps inside a block;
57
- *
58
- * Use this plugin if:
59
- *
60
- * * you need repeat a set of actions in flaky tests
61
- * * iframe was not rendered and you need to retry switching to it
62
- *
63
- *
64
- * #### Configuration
65
- *
66
- * * `pollInterval` - default interval between retries in ms. 200 by default.
67
- * * `registerGlobal` - to register `retryTo` function globally, true by default
68
- *
69
- * If `registerGlobal` is false you can use retryTo from the plugin:
70
- *
71
- * ```js
72
- * const retryTo = codeceptjs.container.plugins('retryTo');
73
- * ```
74
- *
75
- */
76
- module.exports = function (config) {
77
- config = Object.assign(defaultConfig, config)
78
- function retryTo(callback, maxTries, pollInterval = config.pollInterval) {
79
- return new Promise((done, reject) => {
80
- let tries = 1
81
-
82
- function handleRetryException(err) {
83
- recorder.throw(err)
84
- reject(err)
85
- }
86
-
87
- const tryBlock = async () => {
88
- tries++
89
- recorder.session.start(`retryTo ${tries}`)
90
- try {
91
- await callback(tries)
92
- } catch (err) {
93
- handleRetryException(err)
94
- }
95
-
96
- // Call done if no errors
97
- recorder.add(() => {
98
- recorder.session.restore(`retryTo ${tries}`)
99
- done(null)
100
- })
101
-
102
- // Catch errors and retry
103
- recorder.session.catch(err => {
104
- recorder.session.restore(`retryTo ${tries}`)
105
- if (tries <= maxTries) {
106
- debug(`Error ${err}... Retrying`)
107
- recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval))
108
- } else {
109
- // if maxTries reached
110
- handleRetryException(err)
111
- }
112
- })
113
- }
114
-
115
- recorder.add('retryTo', tryBlock).catch(err => {
116
- console.error('An error occurred:', err)
117
- done(null)
118
- })
119
- })
120
- }
121
-
122
- if (config.registerGlobal) {
123
- global.retryTo = retryTo
124
- }
125
-
126
- return retryTo
1
+ module.exports = function () {
2
+ console.log(`
3
+ Deprecated Warning: 'retryTo' has been moved to the effects module.
4
+ You should update your tests to use it as follows:
5
+
6
+ \`\`\`javascript
7
+ const { retryTo } = require('codeceptjs/effects');
8
+
9
+ // Example: Retry these steps 5 times before failing
10
+ await retryTo((tryNum) => {
11
+ I.switchTo('#editor frame');
12
+ I.click('Open');
13
+ I.see('Opened');
14
+ }, 5);
15
+ \`\`\`
16
+
17
+ For more details, refer to the documentation.
18
+ `)
127
19
  }
@@ -1,115 +1,17 @@
1
- const recorder = require('../recorder')
2
- const { debug } = require('../output')
1
+ module.exports = function () {
2
+ console.log(`
3
+ Deprecated Warning: 'tryTo' has been moved to the effects module.
4
+ You should update your tests to use it as follows:
3
5
 
4
- const defaultConfig = {
5
- registerGlobal: true,
6
- }
7
-
8
- /**
9
- *
10
- *
11
- * Adds global `tryTo` function in which all failed steps won't fail a test but will return true/false.
12
- *
13
- * Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
14
- *
15
- * ```js
16
- * plugins: {
17
- * tryTo: {
18
- * enabled: true
19
- * }
20
- * }
21
- * ```
22
- * Use it in your tests:
23
- *
24
- * ```js
25
- * const result = await tryTo(() => I.see('Welcome'));
26
- *
27
- * // if text "Welcome" is on page, result => true
28
- * // if text "Welcome" is not on page, result => false
29
- * ```
30
- *
31
- * Disables retryFailedStep plugin for steps inside a block;
32
- *
33
- * Use this plugin if:
34
- *
35
- * * you need to perform multiple assertions inside a test
36
- * * there is A/B testing on a website you test
37
- * * there is "Accept Cookie" banner which may surprisingly appear on a page.
38
- *
39
- * #### Usage
40
- *
41
- * #### Multiple Conditional Assertions
42
- *
43
- * ```js
44
- *
45
- * Add assert requires first:
46
- * ```js
47
- * const assert = require('assert');
48
- * ```
49
- * Then use the assertion:
50
- * const result1 = await tryTo(() => I.see('Hello, user'));
51
- * const result2 = await tryTo(() => I.seeElement('.welcome'));
52
- * assert.ok(result1 && result2, 'Assertions were not succesful');
53
- * ```
54
- *
55
- * ##### Optional click
56
- *
57
- * ```js
58
- * I.amOnPage('/');
59
- * tryTo(() => I.click('Agree', '.cookies'));
60
- * ```
61
- *
62
- * #### Configuration
63
- *
64
- * * `registerGlobal` - to register `tryTo` function globally, true by default
65
- *
66
- * If `registerGlobal` is false you can use tryTo from the plugin:
67
- *
68
- * ```js
69
- * const tryTo = codeceptjs.container.plugins('tryTo');
70
- * ```
71
- *
72
- */
73
- module.exports = function (config) {
74
- config = Object.assign(defaultConfig, config)
6
+ \`\`\`javascript
7
+ const { tryTo } = require('codeceptjs/effects');
75
8
 
76
- if (config.registerGlobal) {
77
- global.tryTo = tryTo
78
- }
79
- return tryTo
80
- }
9
+ // Example: failed step won't fail a test but will return true/false
10
+ await tryTo(() => {
11
+ I.switchTo('#editor frame');
12
+ });
13
+ \`\`\`
81
14
 
82
- function tryTo(callback) {
83
- let result = false
84
- return recorder.add(
85
- 'tryTo',
86
- () => {
87
- recorder.session.start('tryTo')
88
- process.env.TRY_TO = 'true'
89
- callback()
90
- recorder.add(() => {
91
- result = true
92
- recorder.session.restore('tryTo')
93
- return result
94
- })
95
- recorder.session.catch(err => {
96
- result = false
97
- const msg = err.inspect ? err.inspect() : err.toString()
98
- debug(`Unsuccessful try > ${msg}`)
99
- recorder.session.restore('tryTo')
100
- return result
101
- })
102
- return recorder.add(
103
- 'result',
104
- () => {
105
- process.env.TRY_TO = undefined
106
- return result
107
- },
108
- true,
109
- false,
110
- )
111
- },
112
- false,
113
- false,
114
- )
15
+ For more details, refer to the documentation.
16
+ `)
115
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.7.0-beta.5",
3
+ "version": "3.7.0-beta.6",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -138,7 +138,7 @@
138
138
  "chai-as-promised": "7.1.2",
139
139
  "chai-subset": "1.6.0",
140
140
  "documentation": "14.0.3",
141
- "electron": "33.2.1",
141
+ "electron": "33.3.1",
142
142
  "eslint": "^9.17.0",
143
143
  "eslint-plugin-import": "2.31.0",
144
144
  "eslint-plugin-mocha": "10.5.0",
@@ -154,7 +154,7 @@
154
154
  "json-server": "0.17.4",
155
155
  "playwright": "1.49.1",
156
156
  "prettier": "^3.3.2",
157
- "puppeteer": "23.11.1",
157
+ "puppeteer": "24.0.0",
158
158
  "qrcode-terminal": "0.12.0",
159
159
  "rosie": "2.1.1",
160
160
  "runok": "0.9.3",