Skip to content

Commit 9d5aca5

Browse files
committed
move tryTo, retryTo to effects
1 parent f33272e commit 9d5aca5

File tree

7 files changed

+326
-334
lines changed

7 files changed

+326
-334
lines changed

lib/effects.js

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,210 @@ async function hopeThat(callback) {
118118
)
119119
}
120120

121+
/**
122+
*
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.
161+
*
162+
*/
163+
async function retryTo(callback, maxTries, pollInterval = 200) {
164+
const sessionName = 'retryTo'
165+
166+
return new Promise((done, reject) => {
167+
let tries = 1
168+
169+
function handleRetryException(err) {
170+
recorder.throw(err)
171+
reject(err)
172+
}
173+
174+
const tryBlock = async () => {
175+
tries++
176+
recorder.session.start(`${sessionName} ${tries}`)
177+
try {
178+
await callback(tries)
179+
} catch (err) {
180+
handleRetryException(err)
181+
}
182+
183+
// Call done if no errors
184+
recorder.add(() => {
185+
recorder.session.restore(`${sessionName} ${tries}`)
186+
done(null)
187+
})
188+
189+
// Catch errors and retry
190+
recorder.session.catch(err => {
191+
recorder.session.restore(`${sessionName} ${tries}`)
192+
if (tries <= maxTries) {
193+
debug(`Error ${err}... Retrying`)
194+
recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval))
195+
} else {
196+
// if maxTries reached
197+
handleRetryException(err)
198+
}
199+
})
200+
}
201+
202+
recorder.add(sessionName, tryBlock).catch(err => {
203+
console.error('An error occurred:', err)
204+
done(null)
205+
})
206+
})
207+
}
208+
209+
/**
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+
* ```
263+
*
264+
* @async
265+
* @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.
268+
*
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');
277+
*
278+
* @example
279+
* // Optional Click
280+
* const { tryTo } = require('codeceptjs/effects');
281+
*
282+
* I.amOnPage('/');
283+
* await tryTo(() => I.click('Agree', '.cookies'));
284+
*/
285+
async function tryTo(callback) {
286+
if (store.dryRun) return
287+
const sessionName = 'tryTo'
288+
289+
let result = false
290+
return recorder.add(
291+
sessionName,
292+
() => {
293+
recorder.session.start(sessionName)
294+
store.tryTo = true
295+
callback()
296+
recorder.add(() => {
297+
result = true
298+
recorder.session.restore(sessionName)
299+
return result
300+
})
301+
recorder.session.catch(err => {
302+
result = false
303+
const msg = err.inspect ? err.inspect() : err.toString()
304+
debug(`Unsuccessful try > ${msg}`)
305+
recorder.session.restore(sessionName)
306+
return result
307+
})
308+
return recorder.add(
309+
'result',
310+
() => {
311+
store.tryTo = undefined
312+
return result
313+
},
314+
true,
315+
false,
316+
)
317+
},
318+
false,
319+
false,
320+
)
321+
}
322+
121323
module.exports = {
122324
hopeThat,
325+
retryTo,
326+
tryTo,
123327
}

lib/plugin/retryTo.js

Lines changed: 18 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
`)
12719
}

0 commit comments

Comments
 (0)