{"id":15553503,"url":"https://github.com/bahmutov/cypress-recurse","last_synced_at":"2025-10-12T18:30:15.389Z","repository":{"id":37040167,"uuid":"343536410","full_name":"bahmutov/cypress-recurse","owner":"bahmutov","description":"A way to re-run Cypress commands until a predicate function returns true","archived":false,"fork":false,"pushed_at":"2024-04-25T03:00:23.000Z","size":2169,"stargazers_count":119,"open_issues_count":3,"forks_count":11,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-05-01T23:49:45.910Z","etag":null,"topics":["cypress-plugin"],"latest_commit_sha":null,"homepage":"https://glebbahmutov.com/blog/cypress-recurse/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bahmutov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-01T19:43:08.000Z","updated_at":"2024-05-09T08:27:59.960Z","dependencies_parsed_at":"2023-11-30T08:24:38.891Z","dependency_job_id":"94678997-e5b6-4c14-887d-98d6e7f1d162","html_url":"https://github.com/bahmutov/cypress-recurse","commit_stats":{"total_commits":283,"total_committers":9,"mean_commits":"31.444444444444443","dds":0.519434628975265,"last_synced_commit":"13858ed074ea0362eebf6334626097cd486129b8"},"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahmutov%2Fcypress-recurse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahmutov%2Fcypress-recurse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahmutov%2Fcypress-recurse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahmutov%2Fcypress-recurse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bahmutov","download_url":"https://codeload.github.com/bahmutov/cypress-recurse/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":236261750,"owners_count":19120767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cypress-plugin"],"created_at":"2024-10-02T14:35:15.912Z","updated_at":"2025-10-12T18:30:15.382Z","avatar_url":"https://github.com/bahmutov.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cypress-recurse\n\n[![ci status][ci image]][ci url] [![renovate-app badge][renovate-badge]][renovate-app] ![cypress version](https://img.shields.io/badge/cypress-15.4.0-brightgreen) [![cypress-recurse](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/count/tbtscx/main\u0026style=flat\u0026logo=cypress)](https://dashboard.cypress.io/projects/tbtscx/runs)\n\n\u003e A way to re-run Cypress commands until a predicate function returns true\n\nJump to: [Options](#options), [Examples](#examples), [Debugging](#debugging), [Videos](#videos).\n\n## Install\n\n```shell\nnpm i -D cypress-recurse\n# or use Yarn\nyarn add -D cypress-recurse\n```\n\n## Use\n\n### Use a named function\n\n```js\nimport { recurse } from 'cypress-recurse'\n\nit('gets 7', () =\u003e {\n  recurse(\n    () =\u003e cy.task('randomNumber'),\n    (n) =\u003e n === 7,\n  )\n})\n```\n\nThe 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](./cypress/e2e/expect-spec.js).\n\n```js\nit('works for 4', () =\u003e {\n  recurse(\n    () =\u003e cy.wrap(4),\n    (x) =\u003e {\n      expect(x).to.equal(4)\n    },\n  ).should('equal', 4)\n})\n```\n\n**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.\n\n### Use a custom command\n\nOptionally, you can register `cy.recurse` custom command by importing the `cypress-recurse/commands` from your support file or individual specs.\n\n```js\n// cypress/support/e2e.js\nimport 'cypress-recurse/commands'\n// your E2E specs\nit('works', () =\u003e {\n  cy.recurse(...)\n})\n```\n\nParameters to the `cy.recurse` are the same as for named function: the command function, the predicate, followed by the options. For example, to check if the loader goes away after clicking on a button:\n\n```js\ncy.recurse(\n  () =\u003e {\n    cy.get('button').click()\n    return cy.get('.loader').should(Cypress._.noop)\n  },\n  ($el) =\u003e $el.length === 0,\n  {\n    log: false,\n    delay: 1000,\n  },\n)\n```\n\n## Yields\n\nThe `recurse` function yields the subject of the command function.\n\n```js\nimport { recurse } from 'cypress-recurse'\n\nit('gets 7', () =\u003e {\n  recurse(\n    () =\u003e cy.wrap(7),\n    (n) =\u003e n === 7,\n  ).should('equal', 7)\n})\n```\n\n## Options\n\n```js\nit('gets 7 after 50 iterations or 30 seconds', () =\u003e {\n  recurse(\n    () =\u003e cy.task('randomNumber'),\n    (n) =\u003e n === 7,\n    {\n      log: true,\n      limit: 50, // max number of iterations\n      timeout: 30000, // time limit in ms\n      delay: 300, // delay before next iteration, ms\n    },\n  )\n})\n```\n\nYou can see the default options\n\n```js\nimport { RecurseDefaults } from 'cypress-recurse'\n```\n\n### log\n\nThe log option can be a boolean flag or your own function. For example to pretty-print each number we could:\n\n```js\nrecurse(\n  () =\u003e {...},\n  (x) =\u003e x === 3,\n  {\n    log: (k) =\u003e cy.log(`k = **${k}**`),\n  }\n)\n```\n\nYou can simply print a given string at the successful end of the recursion\n\n```js\nrecurse(\n  () =\u003e {...},\n  (x) =\u003e x === 3,\n  {\n    log: 'got to 3!',\n  }\n)\n```\n\nIf the `log` option is a function, it receives the current value, plus a data object with main iteration properties\n\n```js\nlog (x, data) {\n  // data is like:\n  //  value: 3\n  //  successful: false|true\n  //  iteration: 3\n  //  limit: 18\n  //  elapsed: 1631\n  //  elapsedDuration: \"2 seconds\"\n}\n```\n\nSee the [log-spec.js](./cypress/e2e/log-spec.js)\n\n### post\n\nIf 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:\n\n```js\n// from the application's window ping a non-existent URL\nconst url = 'https://jsonplaceholder.cypress.io/fake-endpoint'\nconst checkApi = () =\u003e cy.window().invoke('fetch', url)\n\nrecurse(checkApi, ({ ok }) =\u003e ok, {\n  post: ({ limit, value }) =\u003e {\n    // after a few attempts\n    // stub the network call and respond\n    if (limit === 1) {\n      // start intercepting now\n      console.log('start intercepting')\n      return cy.intercept('GET', url, 'Hello!').as('hello')\n    }\n    // you can use the value prop to look at the fetch results\n  },\n})\n```\n\nThe argument is a single object with `iteration`, `limit`, `value`, `reduced`, `success`, `elapsed`, and `elapsedDuration` properties.\n\nSee the [post-spec.js](./cypress/e2e/post-spec.js) and [find-on-page/spec.js](./cypress/e2e/find-on-page/spec.js).\n\nBy default, the last value is NOT passed to the `post` callback. You can pass the last value by setting an option\n\n```js\nrecurse(fn1, predicate, {\n  post () {\n    ...\n  },\n  postLastValue: true\n})\n```\n\nA good combination is `postLastValue: true` and `post({ value, success })` where the `value` is yielded by the first function, and the `success` is the result of checking that value using the predicate function.\n\n**Note:** if you specify both the delay and the `post` options, the delay runs first.\n\n### custom error message\n\nUse 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.\n\n```js\nrecurse(\n  () =\u003e {...},\n  (x) =\u003e x === 3,\n  {\n    error: 'x never got to 3!',\n  }\n)\n```\n\n### failFast\n\nSometimes the action function yields a value that is obviously wrong and should fail the test. You can check the produced value using a synchronous predicate function. It will be called _before_ the predicate function.\n\n```js\nrecurse(\n  () =\u003e 42,\n  (n) =\u003e n === 42,\n  {\n    // return true to fail the test immediately\n    failFast: (x) =\u003e typeof x !== 'number',\n  },\n)\n```\n\nIn the scenario above, the action yields `42`. The `failFast` checks if its argument is a number (it is), so it returns `false`. The value is then passed to the predicate function `n =\u003e n === 42`. See the spec file [fail-fast-spec.js](./cypress/e2e/fail-fast-spec.js)\n\n### accumulator\n\nSimilar to reducing an array, the `reduce` function has an option to accumulate / reduce the values in the given object. The following options work together\n\n- `reduceFrom` is the starting value, like `[]`\n- `reduce(acc, item)` receives each value and the current accumulator value\n- `reduceLastValue` is false by default, turn it on to call the the `reduce` function with the last value (for which the predicate function has returned true)\n\n```js\nit('uses a local variable to store the results', () =\u003e {\n  const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n  let sum = 0\n\n  recurse(\n    () =\u003e cy.wrap(items.shift()),\n    // the last number is not included\n    (item) =\u003e item === 10,\n    {\n      log: false,\n      delay: 0,\n      post({ value }) {\n        sum += value\n      },\n    },\n  ).then(() =\u003e {\n    expect(sum, 'sum 1 to 9').to.equal(45)\n  })\n})\n\nit('uses an accumulator and yields the sum', () =\u003e {\n  const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n  recurse(\n    () =\u003e cy.wrap(items.shift()),\n    // the last number is not included\n    (item) =\u003e item === 10,\n    {\n      log: false,\n      delay: 0,\n      // start with value of 0\n      reduceFrom: 0,\n      // and add each item produced by the first function\n      reduce(acc, item) {\n        return acc + item\n      },\n      // and yield the reduced value\n      yield: 'reduced',\n    },\n  )\n    // the sum is yielded as the subject\n    // no need for a cy.then(callback)\n    .should('equal', 45)\n})\n\nit('uses an accumulator including the last value', () =\u003e {\n  const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n  recurse(\n    () =\u003e cy.wrap(items.shift()),\n    // 10 will be the last value\n    (item) =\u003e item === 10,\n    {\n      log: false,\n      delay: 0,\n      // start with value of 0\n      reduceFrom: 0,\n      // and include the value that made the predicate return true\n      reduceLastValue: true,\n      // and add each item produced by the first function\n      reduce(acc, item) {\n        return acc + item\n      },\n      // and yield the reduced value\n      yield: 'reduced',\n    },\n  ).should('equal', 55)\n  cy.wrap(items, 'items were consumed').should('deep.equal', [])\n})\n```\n\nIf there is a reduced value, it will be passed as the second argument to the predicate function.\n\n```js\nit('uses the accumulated value in the predicate', () =\u003e {\n  const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n  recurse(\n    () =\u003e cy.wrap(items.shift()),\n    (item, acc) =\u003e acc \u003e 30,\n    {\n      log: false,\n      delay: 0,\n      // start with value of 0\n      reduceFrom: 0,\n      // and add each item produced by the first function\n      reduce(acc, item) {\n        return acc + item\n      },\n      // and yield the reduced value\n      yield: 'reduced',\n    },\n  ).should('equal', 36) // 1+2+...+8 = 36\n  // 9 was the item that made the predicate return true\n  // but it was not included in the sum after being removed\n  // so the only item left in the array is 10\n  cy.wrap(items, 'remaining items').should('deep.equal', [10])\n})\n```\n\nAccumulator could be a collection object, for example\n\n```js\nit('reverses an array', () =\u003e {\n  const items = ['a', 'b', 'c', 'd', 'e']\n  recurse(\n    () =\u003e cy.wrap(items.shift()),\n    (x) =\u003e x === undefined,\n    {\n      log: false,\n      delay: 0,\n      // accumulate all items into an array\n      reduceFrom: [],\n      reduce(acc, item) {\n        acc.unshift(item)\n        return acc\n      },\n      yield: 'reduced',\n    },\n  ).should('deep.equal', ['e', 'd', 'c', 'b', 'a'])\n})\n```\n\nSee the [reduce-spec.js](./cypress/e2e/reduce-spec.js) for more examples.\n\n### yield\n\nIf you are accumulating a reduced value, you can yield it instead of the last value. You can even yield both the last and the reduced values.\n\n- `yield: \"value\"` yields the value that passed the predicate function\n- `yield: \"reduced\"` yields the accumulated value\n- `yield: \"both\"` yields an object with `value` and `reduced` properties\n\nSee the [reduce-spec.js](./cypress/e2e/reduce-spec.js) for examples.\n\n### doNotFail\n\nSometimes you want to retry N times or for M seconds, but not fail the test if the predicate is still false.\n\n```js\nrecurse(commandFn, predicate, {\n  doNotFail: true,\n})\n```\n\nThe yielded value in this case is not guaranteed. You can yield the last value, even if it does not pass the predicate by explicitly asking for it\n\n```js\nrecurse(\n  () =\u003e cy.wrap(4),\n  (x) =\u003e x === 10,\n  {\n    doNotFail: true,\n    yield: 'value',\n  },\n).should('equal', 4)\n```\n\n## each\n\nThis plugin also includes the `each` function that iterates over the given subject items. It can optionally stop when the separate predicate function returns true.\n\n```js\nimport { each } from 'cypress-recurse'\nit('iterates until it finds the value 7', () =\u003e {\n  cy.get('li').then(\n    each(\n      $li =\u003e ..., // do something with the item\n      $li =\u003e $li.text() === '7' // stop if we see \"7\"\n    )\n  )\n})\n```\n\nThe `each` function yields the original or transformed items\n\n```js\nconst numbers = [1, 2, 3, 4]\ncy.wrap(numbers)\n  .then(\n    each(\n      (x) =\u003e {\n        return 10 + x\n      },\n      // stop when the value is 13\n      (x) =\u003e x === 13,\n    ),\n  )\n  .should('deep.equal', [11, 12])\n```\n\nSee the [each-spec.js](./cypress/e2e/each/each-spec.js) file.\n\n## retry\n\n**Experimental:** this function can change its API at any moment.\n\nIf you need retries in your config / plugins code, you can use the included `retry` function.\n\n```js\n// your cypress.config.js\nimport {retry} from 'cypress-recurse/src/retry.js'\n// or use require\nconst {retry} = require('cypress-recurse/src/retry')\n\nasync function getData() {\n  // your async function that returns data\n  // let's say it is a number\n  return n\n}\n\ne2e: {\n  setupNodeEvents(on, config) {\n    on('task', {\n      async fetchData () {\n        // we want to retry \"getData\" function\n        // until it returns a value above 200\n        const data = await retry(getData, n =\u003e n \u003e 200, {\n          limit: 10, // limit calling getData to 10 times\n          delay: 100 // delay 100ms between attempts\n        })\n        return data\n      }\n    })\n  },\n},\n```\n\n### retry options\n\n```js\nretry(fn, predicateFn, options?)\n```\n\nOptions object can have the following properties\n\n- `limit` the maximum number of attempts to call the given function\n- `delay` in milliseconds between calls to `fn`\n- `log` log individual calls to `fn` (by default the logging is off). Could be your own function (see below)\n- `extract` a custom function that takes the result of the `fn` and returns the value to yield\n\n**Example:** retry until the list is non-empty, then return a property from the first object\n\n```js\nconst n = retry(fn, (list) =\u003e list.length, {\n  extract: (list) =\u003e list[0].n,\n})\n```\n\n**Example:** custom log function\n\n```js\n// user log function receives these arguments\nconst log = ({ attempt, limit, value, successful }) =\u003e {\n  console.log(\n    'attempt %d of %d, value %o success: %o',\n    attempt,\n    limit,\n    value,\n    successful,\n  )\n}\nretry(fn, predicate, { log })\n```\n\n## Examples\n\n- clear and type text into the input field until it has the expected value, see [type-with-retries-spec.js](./cypress/e2e/type-with-retries-spec.js), watch the video [Avoid Flake When Typing Into The Input Elements Using cypress-recurse](https://youtu.be/aYX7OVqp6AE) and read the blog post [Solve Flake In Cypress Typing Into An Input Element\n  ](https://glebbahmutov.com/blog/flaky-cy-type/)\n- [avoid-while-loops-in-cypress](https://github.com/bahmutov/avoid-while-loops-in-cypress) repo\n- [monalego](https://github.com/bahmutov/monalego) repo and [Canvas Visual Testing with Retries](https://glebbahmutov.com/blog/canvas-testing/) blog post, watch [the video](https://www.youtube.com/watch?v=xSK6fe5WD1g)\n- [reloading the page until it shows the expected text](./cypress/e2e/reload-page/reload-spec.js) recipe\n- [pinging the API endpoint until it responds](https://youtu.be/CU8C6MRP_GU)\n- [HTML canvas bar chart testing](https://youtu.be/aeBclf9A92A)\n- [Browse Reveal.js Slides Using Cypress and cypress-recurse](https://youtu.be/oq2P1wtIZYY)\n- opening each accordion panel until we find a button to click [accordion-spec.js](./cypress/e2e/accordion-spec.js), see video [Use cypress-recurse To Open Accordion Panels Until It Finds A Button To Click](https://youtu.be/s2_467yUF2Y)\n\n## Debugging\n\nUse options `log: true` and `debugLog: true` to print additional information to the Command Log\n\n```js\nrecurse(getTo(2), (x) =\u003e x === 2, {\n  timeout: 1000,\n  limit: 3,\n  delay: 100,\n  log: true,\n  debugLog: true,\n}).should('equal', 2)\n```\n\n![Debug logs](./images/debug-log.png)\n\n## Blog posts\n\n📝 Read the following posts\n\n- [Writing Cypress Recurse Function](https://glebbahmutov.com/blog/cypress-recurse/)\n- [Crawl Weather Using Cypress](https://glebbahmutov.com/blog/crawl-weather/)\n- [Cypress And Twilio](https://glebbahmutov.com/blog/cypress-twilio/)\n- [Retry Network Requests](https://glebbahmutov.com/blog/retry-network-requests/)\n\n**Tip:** use [https://cypress.tips/search](https://cypress.tips/search) to search all my testing content\n\n## Videos\n\nI have explained how this module was written in the [following videos](https://www.youtube.com/playlist?list=PLP9o9QNnQuAbegJlN5ZTRxqtUBtKwXOHQ)\n\n1. [Call cy task until it returns an expected value](https://youtu.be/r8_hFwYAo5c)\n2. [Reusable recursive function](https://www.youtube.com/watch?v=Q_7-gRQLLMA)\n3. [Reusable function with attempts limit](https://www.youtube.com/watch?v=I1oNKD6NNjg)\n4. [Recursion function with time limit](https://www.youtube.com/watch?v=Cn8Ubhd49Gw)\n5. [Convert recurse to use options object](https://youtu.be/DeMRtTD5p7s)\n6. [Add JSDoc types to the options parameter](https://youtu.be/g4qispkHH-o)\n7. [Published cypress-recurse NPM package](https://www.youtube.com/watch?v=V82p7qTowXg)\n\n### Courses\n\n🎓 This plugin is covered in multiple lessons in my [Cypress plugins course](https://cypress.tips/courses/cypress-plugins)\n\n- [Lesson i1: Check the list is sorted after some delay](https://cypress.tips/courses/cypress-plugins/lessons/i1)\n- [Lesson i2: Call the API until it returns the expected result](https://cypress.tips/courses/cypress-plugins/lessons/i2)\n- [Lesson i6: Delete the first todo item until there are no more items](https://cypress.tips/courses/cypress-plugins/lessons/i6)\n- [Lesson i7: find row in virtualized table](https://cypress.tips/courses/cypress-plugins/lessons/i7)\n- [Lesson i8: Randomly pick a menu](https://cypress.tips/courses/cypress-plugins/lessons/i8)\n- [Lesson i10: Recursively delete items while the page reloads](https://cypress.tips/courses/cypress-plugins/lessons/i10)\n- [Lesson i11: Repeatedly check the task status](https://cypress.tips/courses/cypress-plugins/lessons/i11)\n- [Lesson n3: Pagination using cypress-recurse](https://cypress.tips/courses/cypress-plugins/lessons/n3)\n- [Lesson o2: Periodically check LowDB data until the record is found](https://cypress.tips/courses/cypress-plugins/lessons/o2)\n- [Lesson o3: Retry checking LowDB inside the task code](https://cypress.tips/courses/cypress-plugins/lessons/o3)\n- [Lesson o10: Custom error message](https://cypress.tips/courses/cypress-plugins/lessons/o10)\n\n🎓 This plugin was used in my course [Cypress Network Testing Exercises](https://cypress.tips/courses/network-testing)\n\n- [Bonus 2: Collect all fruits using cypress-recurse plugin](https://cypress.tips/courses/network-testing/lessons/bonus02)\n- [Bonus 27: Use cypress-recurse to ping the endpoint until it succeeds](https://cypress.tips/courses/network-testing/lessons/bonus27)\n- [Bonus 33: Query APIs to check the 3rd party services](https://cypress.tips/courses/network-testing/lessons/bonus33)\n- [Bonus 34: Print messages to the terminal when retrying cy.request calls](https://cypress.tips/courses/network-testing/lessons/bonus34)\n- [Bonus 38: Check all intercepted network calls using cypress-recurse plugin](https://cypress.tips/courses/network-testing/lessons/bonus38)\n- [Bonus 63: Retry calling an API endpoint and log each attempt](https://cypress.tips/courses/network-testing/lessons/bonus63)\n- [Bonus 141: Ping API instead of hard-coded wait](https://cypress.tips/courses/network-testing/lessons/bonus141)\n- [Bonus 143: Retry creating an item via flaky endpoint](https://cypress.tips/courses/network-testing/lessons/bonus143)\n- [Bonus 147: Fail fast for recursion](https://cypress.tips/courses/network-testing/lessons/bonus147)\n\n### Bonus videos\n\n1. [use cypress-recurse to find the downloaded file](https://www.youtube.com/watch?v=Ty5ltRdgr5M)\n2. [canvas visual testing](https://www.youtube.com/watch?v=xSK6fe5WD1g)\n3. [wait for API to respond](https://www.youtube.com/watch?v=CU8C6MRP_GU)\n4. [get to the last page](https://youtu.be/6-xHHtAzNtk) by clicking the \"Next\" button\n5. [Use cypress-recurse To Scroll The Page Until It Loads The Text We Are Looking For](https://youtu.be/KHn7647xOz8)\n6. [Use cypress-recurse Plugin To Confirm The Table Gets Sorted Eventually](https://youtu.be/Ke5Pf6IISn8)\n7. [Use cypress-recurse To Ping The Site Before Visiting It](https://youtu.be/8rtBk9MBjXA)\n8. [Use cypress-recurse To Click The Back Button Until It No Longer Exists](https://youtu.be/G-4HbUuUqIM)\n9. [Reload The Page Until We See 7 Plus Check The Numbers Before That](https://youtu.be/KHJkRp_rRYg)\n10. [Click On The Button Until It Becomes Disabled](https://youtu.be/u2JUQY2TE3A)\n11. [Stub cy.request Command Using cy.stub And Use cypress-recurse Example](https://youtu.be/rFhTejdPGAM)\n12. [Use cypress-recurse To Open Accordion Panels Until It Finds A Button To Click](https://youtu.be/s2_467yUF2Y)\n13. [Access The Response Text Yielded By The Plugin cypress-response](https://youtu.be/MRelgoMg230)\n14. [Cypress Asynchronous Unit Tests Using Sinon.js And Chai](https://youtu.be/2rn3fsR8xp0)\n15. [Go To The Previous Page While Possible Using cypress-recurse](https://youtu.be/B_oOHtuUJwc)\n16. [Test Carousel Captions](https://youtu.be/sNw86J1M-go)\n17. [Accumulator And Cypress-Recurse Plugin](https://youtu.be/6XdBrqrlBIU)\n\n## Small print\n\nAuthor: Gleb Bahmutov \u0026lt;gleb.bahmutov@gmail.com\u0026gt; \u0026copy; 2021\n\n- [@bahmutov](https://twitter.com/bahmutov)\n- [glebbahmutov.com](https://glebbahmutov.com)\n- [blog](https://glebbahmutov.com/blog)\n- [videos](https://www.youtube.com/glebbahmutov)\n- [presentations](https://slides.com/bahmutov)\n- [cypress.tips](https://cypress.tips)\n\nLicense: MIT - do anything with the code, but don't blame me if it does not work.\n\nSupport: if you find any problems with this module, email / tweet /\n[open issue](https://github.com/bahmutov/cypress-recurse/issues) on Github\n\n## MIT License\n\nCopyright (c) 2020 Gleb Bahmutov \u0026lt;gleb.bahmutov@gmail.com\u0026gt;\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\n[ci image]: https://github.com/bahmutov/cypress-recurse/workflows/ci/badge.svg?branch=main\n[ci url]: https://github.com/bahmutov/cypress-recurse/actions\n[renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg\n[renovate-app]: https://renovateapp.com/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbahmutov%2Fcypress-recurse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbahmutov%2Fcypress-recurse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbahmutov%2Fcypress-recurse/lists"}