Skip to content

Commit dad4628

Browse files
committed
address CR
1 parent 4b012b7 commit dad4628

File tree

2 files changed

+91
-197
lines changed

2 files changed

+91
-197
lines changed

lib/effects.js

Lines changed: 66 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,33 @@
11
const recorder = require('./recorder')
22
const { debug } = require('./output')
33
const store = require('./store')
4+
const event = require('./event')
45

56
/**
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.
619
*
6210
* @async
6311
* @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.
66-
*
67-
* @example
68-
* // Multiple Conditional Assertions
69-
* const assert = require('assert');
70-
* const { hopeThat } = require('codeceptjs/effects');
71-
*
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');
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.
7523
*
7624
* @example
77-
* // Optional Click
78-
* const { hopeThat } = require('codeceptjs/effects');
25+
* const { hopeThat } = require('codeceptjs/effects')
26+
* await hopeThat(() => {
27+
* I.see('Welcome'); // Perform a soft assertion
28+
* });
7929
*
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.
8231
*/
8332
async function hopeThat(callback) {
8433
if (store.dryRun) return
@@ -100,6 +49,9 @@ async function hopeThat(callback) {
10049
result = false
10150
const msg = err.inspect ? err.inspect() : err.toString()
10251
debug(`Unsuccessful assertion > ${msg}`)
52+
event.dispatcher.on(event.test.after, test => {
53+
test.notes.push({ type: 'conditionalError', text: msg })
54+
})
10355
recorder.session.restore(sessionName)
10456
return result
10557
})
@@ -119,46 +71,33 @@ async function hopeThat(callback) {
11971
}
12072

12173
/**
74+
* A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
12275
*
123-
* @module retryTo
124-
*
125-
* `retryTo` which retries steps a few times before failing.
126-
*
127-
*
128-
* Use it in your tests:
129-
*
130-
* const { retryTo } = require('codeceptjs/effects');
131-
* ```js
132-
* // retry these steps 5 times before failing
133-
* await retryTo((tryNum) => {
134-
* I.switchTo('#editor frame');
135-
* I.click('Open');
136-
* I.see('Opened')
137-
* }, 5);
138-
* ```
139-
* Set polling interval as 3rd argument (200ms by default):
140-
*
141-
* ```js
142-
* // retry these steps 5 times before failing
143-
* await retryTo((tryNum) => {
144-
* I.switchTo('#editor frame');
145-
* I.click('Open');
146-
* I.see('Opened')
147-
* }, 5, 100);
148-
* ```
149-
*
150-
* Disables retryFailedStep plugin for steps inside a block;
151-
*
152-
* Use this plugin if:
153-
*
154-
* * you need repeat a set of actions in flaky tests
155-
* * iframe was not rendered, so you need to retry switching to it
156-
*
157-
*
158-
* #### Configuration
159-
*
160-
* * `pollInterval` - default interval between retries in ms. 200 by default.
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.
16189
*
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.
162101
*/
163102
async function retryTo(callback, maxTries, pollInterval = 200) {
164103
const sessionName = 'retryTo'
@@ -207,80 +146,32 @@ async function retryTo(callback, maxTries, pollInterval = 200) {
207146
}
208147

209148
/**
210-
* @module tryTo
211-
*
212-
* `tryTo` which all failed steps won't fail a test but will return true/false.
213-
* It enables conditional assertions without terminating the test upon failure.
214-
* This is particularly useful in scenarios like A/B testing, handling unexpected elements,
215-
* or performing multiple assertions where you want to collect all results before deciding
216-
* on the test outcome.
217-
*
218-
* ## Use Cases
219-
*
220-
* - **Multiple Conditional Assertions**: Perform several assertions and evaluate all their outcomes together.
221-
* - **A/B Testing**: Handle different variants in A/B tests without failing the entire test upon one variant's failure.
222-
* - **Unexpected Elements**: Manage elements that may or may not appear, such as "Accept Cookie" banners.
223-
*
224-
* ## Examples
225-
*
226-
* ### Multiple Conditional Assertions
227-
*
228-
* Add the assertion library:
229-
* ```js
230-
* const assert = require('assert');
231-
* const { tryTo } = require('codeceptjs/effects');
232-
* ```
233-
*
234-
* Use `hopeThat` with assertions:
235-
* ```js
236-
* const result1 = await tryTo(() => I.see('Hello, user'));
237-
* const result2 = await tryTo(() => I.seeElement('.welcome'));
238-
* assert.ok(result1 && result2, 'Assertions were not successful');
239-
* ```
240-
*
241-
* ### Optional Click
242-
*
243-
* ```js
244-
* const { tryTo } = require('codeceptjs/effects');
245-
*
246-
* I.amOnPage('/');
247-
* await tryTo(() => I.click('Agree', '.cookies'));
248-
* ```
249-
*
250-
* This function records the execution of a callback containing assertion logic.
251-
* If the assertion fails, it logs the failure without stopping the test execution.
252-
* It is useful for scenarios where multiple assertions are performed, and you want
253-
* to evaluate all outcomes before deciding on the test result.
254-
*
255-
* ## Usage
256-
*
257-
* ```js
258-
* const result = await tryTo(() => I.see('Welcome'));
259-
*
260-
* // If the text "Welcome" is on the page, result => true
261-
* // If the text "Welcome" is not on the page, result => false
262-
* ```
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.
263151
*
264152
* @async
265153
* @function tryTo
266-
* @param {Function} callback - The callback function.
267-
* @returns {Promise<boolean | any>} - Resolves to `true` if the assertion is successful, or `false` if it fails.
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.
268157
*
269-
* @example
270-
* // Multiple Conditional Assertions
271-
* const assert = require('assert');
272-
* const { tryTo } = require('codeceptjs/effects');
273-
*
274-
* const result1 = await tryTo(() => I.see('Hello, user'));
275-
* const result2 = await tryTo(() => I.seeElement('.welcome'));
276-
* assert.ok(result1 && result2, 'Assertions were not successful');
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.
277163
*
278164
* @example
279-
* // Optional Click
280-
* const { tryTo } = require('codeceptjs/effects');
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+
* }
281173
*
282-
* I.amOnPage('/');
283-
* await tryTo(() => I.click('Agree', '.cookies'));
174+
* @throws Will handle errors internally, logging them and returning `false` as the result.
284175
*/
285176
async function tryTo(callback) {
286177
if (store.dryRun) return

test/unit/effects_test.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const recorder = require('../../lib/recorder')
55
describe('effects', () => {
66
describe('hopeThat', () => {
77
beforeEach(() => {
8+
recorder.reset()
89
recorder.start()
910
})
1011

@@ -25,8 +26,32 @@ describe('effects', () => {
2526
})
2627
})
2728

29+
describe('tryTo', () => {
30+
beforeEach(() => {
31+
recorder.reset()
32+
recorder.start()
33+
})
34+
35+
it('should execute command on success', async () => {
36+
const ok = await tryTo(() => recorder.add(() => 5))
37+
expect(ok).to.be.equal(true)
38+
return recorder.promise()
39+
})
40+
41+
it('should execute command on fail', async () => {
42+
const notOk = await tryTo(() =>
43+
recorder.add(() => {
44+
throw new Error('Ups')
45+
}),
46+
)
47+
expect(false).is.equal(notOk)
48+
return recorder.promise()
49+
})
50+
})
51+
2852
describe('retryTo', () => {
2953
beforeEach(() => {
54+
recorder.reset()
3055
recorder.start()
3156
})
3257

@@ -66,26 +91,4 @@ describe('effects', () => {
6691
expect(errorCaught).is.true
6792
})
6893
})
69-
70-
describe('tryTo', () => {
71-
beforeEach(() => {
72-
recorder.start()
73-
})
74-
75-
it('should execute command on success', async () => {
76-
const ok = await tryTo(() => recorder.add(() => 5))
77-
expect(true).is.equal(ok)
78-
return recorder.promise()
79-
})
80-
81-
it('should execute command on fail', async () => {
82-
const notOk = await tryTo(() =>
83-
recorder.add(() => {
84-
throw new Error('Ups')
85-
}),
86-
)
87-
expect(false).is.equal(notOk)
88-
return recorder.promise()
89-
})
90-
})
9194
})

0 commit comments

Comments
 (0)