@@ -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+
121323module . exports = {
122324 hopeThat,
325+ retryTo,
326+ tryTo,
123327}
0 commit comments