cypress-recurse

A way to re-run Cypress commands until a predicate function returns true

Usage no npm install needed!

<script type="module">
  import cypressRecurse from 'https://cdn.skypack.dev/cypress-recurse';
</script>

README

cypress-recurse

ci status renovate-app badge cypress version cypress-recurse

A way to re-run Cypress commands until a predicate function returns true

Install

npm i -D cypress-recurse
# or use Yarn
yarn add -D cypress-recurse

Use

import { recurse } from 'cypress-recurse'

it('gets 7', () => {
  recurse(
    () => cy.task('randomNumber'),
    (n) => n === 7,
  )
})

The predicate function should return a boolean OR use assertions to throw errors. If the predicate returns undefined, we assume it passes, see examples in expect-spec.js.

it('works for 4', () => {
  recurse(
    () => cy.wrap(4),
    (x) => {
      expect(x).to.equal(4)
    },
  ).should('equal', 4)
})

Important: the commands inside the first function cannot fail - otherwise the entire test fails. Thus make them as "passive" as possible, and let the predicate function decide if the entire function needs to be retried or not.

Yields

The recurse function yields the subject of the command function.

import { recurse } from 'cypress-recurse'

it('gets 7', () => {
  recurse(
    () => cy.wrap(7),
    (n) => n === 7,
  ).should('equal', 7)
})

Options

it('gets 7 after 50 iterations or 30 seconds', () => {
  recurse(
    () => cy.task('randomNumber'),
    (n) => n === 7,
    {
      log: true,
      limit: 50, // max number of iterations
      timeout: 30000, // time limit in ms
      delay: 300 // delay before next iteration, ms
    },
  )
})

You can see the default options

import { RecurseDefaults } from 'cypress-recurse'

log

The log option can be a boolean flag or your own function. For example to pretty-print each number we could:

recurse(
  () => {...},
  (x) => x === 3,
  {
    log: (k) => cy.log(`k = **${k}**`),
  }
)

You can simply print a given string at the successful end of the recursion

recurse(
  () => {...},
  (x) => x === 3,
  {
    log: 'got to 3!',
  }
)

See the log-spec.js

post

If you want to run a few more Cypress commands after the predicate function that are not part of the initial command, use the post option. For example, you can start intercepting the network requests after a few iterations:

// from the application's window ping a non-existent URL
const url = 'https://jsonplaceholder.cypress.io/fake-endpoint'
const checkApi = () => cy.window().invoke('fetch', url)
const isSuccess = ({ ok }) => ok

recurse(checkApi, isSuccess, {
  post: ({ limit }) => {
    // after a few attempts
    // stub the network call and respond
    if (limit === 1) {
      // start intercepting now
      console.log('start intercepting')
      return cy.intercept('GET', url, 'Hello!').as('hello')
    }
  },
})

See the post-spec.js and find-on-page-spec.js.

Note: if you specify both the delay and the post options, the delay runs first.

custom error message

Use the error option if you want to add a custom error message when the recursion timed out or the iteration limit has reached the end.

recurse(
  () => {...},
  (x) => x === 3,
  {
    error: 'x never got to 3!',
  }
)

Examples

Blog post

Read Writing Cypress Recurse Function

Videos

I have explained how this module was written in the following videos

  1. Call cy task until it returns an expected value
  2. Reusable recursive function
  3. Reusable function with attempts limit
  4. Recursion function with time limit
  5. Convert recurse to use options object
  6. Add JSDoc types to the options parameter
  7. Published cypress-recurse NPM package

Bonus videos

  1. use cypress-recurse to find the downloaded file
  2. canvas visual testing
  3. wait for API to respond
  4. get to the last page by clicking the "Next" button
  5. Use cypress-recurse To Scroll The Page Until It Loads The Text We Are Looking For
  6. Use cypress-recurse Plugin To Confirm The Table Gets Sorted Eventually

Small print

Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2021

License: MIT - do anything with the code, but don't blame me if it does not work.

Support: if you find any problems with this module, email / tweet / open issue on Github

MIT License

Copyright (c) 2020 Gleb Bahmutov <gleb.bahmutov@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.