{"id":34115986,"url":"https://github.com/ncjones/retry-assert","last_synced_at":"2026-04-07T04:32:07.015Z","repository":{"id":33659649,"uuid":"157067177","full_name":"ncjones/retry-assert","owner":"ncjones","description":"Retry a function until or while it passes an assertion","archived":false,"fork":false,"pushed_at":"2025-12-03T17:43:54.000Z","size":301,"stargazers_count":8,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-02-07T13:35:06.042Z","etag":null,"topics":["assert","async","ensure","javascript","retry","until","wait"],"latest_commit_sha":null,"homepage":null,"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/ncjones.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2018-11-11T10:03:02.000Z","updated_at":"2025-12-03T17:43:27.000Z","dependencies_parsed_at":"2024-06-21T14:27:58.664Z","dependency_job_id":"9600acdc-cddb-461f-b83a-2cfb046e04b6","html_url":"https://github.com/ncjones/retry-assert","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ncjones/retry-assert","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncjones%2Fretry-assert","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncjones%2Fretry-assert/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncjones%2Fretry-assert/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncjones%2Fretry-assert/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ncjones","download_url":"https://codeload.github.com/ncjones/retry-assert/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ncjones%2Fretry-assert/sbom","scorecard":{"id":677430,"data":{"date":"2025-08-11","repo":{"name":"github.com/ncjones/retry-assert","commit":"bbebb9783cfc40d5f3c89fcf3b371e276590a2c6"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 1/19 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/main.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/ncjones/retry-assert/main.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/ncjones/retry-assert/main.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/ncjones/retry-assert/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/ncjones/retry-assert/release.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T21:56:47.032Z","repository_id":33659649,"created_at":"2025-08-21T21:56:47.032Z","updated_at":"2025-08-21T21:56:47.032Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31500397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["assert","async","ensure","javascript","retry","until","wait"],"created_at":"2025-12-14T20:08:00.249Z","updated_at":"2026-04-07T04:32:07.009Z","avatar_url":"https://github.com/ncjones.png","language":"JavaScript","readme":"Retry Assert\n============\n\nRetry a function, either until or while an assertion passes, seamlessly\nintegrating with Promise-aware test runners such as Mocha, Jest and Cucumber\nJS, producing better failure messages than other similar libraries. Inspired by\n[RSpec-Wait][] and [TryTryAgain][].\n\n\nInstallation\n------------\n\n    npm install retry-assert\n\n\nUsage\n-----\n\n**Retry until**: retry a function until it's returned value passes an\nassertion:\n\n```javascript\nimport retry from 'retry-assert';\nimport { expect } from 'expect';\n\nasync function getUser (id) {\n  console.log('get user');\n  return { id, active: Date.now() % 10 === 0 };\n}\n\n// call the asynchronous \"getUser\" method until user is active:\nconst activeUser = await retry()\n  .fn(() =\u003e getUser(1))\n  .until(user =\u003e expect(user).toHaveProperty('active', true));\nconsole.log(activeUser);\n```\n\n\n**Retry ensure**: retry a function until timeout, failing immediately when an\nassertion fails:\n\n```javascript\nimport retry from 'retry-assert';\nimport { expect } from 'expect';\n\nasync function getUser (id) {\n  console.log('get user');\n  return { id, active: Date.now() % 10 === 0 };\n}\n\n// call the asynchronous \"getUser\" function repeatedly for 2 seconds,\n// ensuring user is not active:\nconst inactiveUser = await retry()\n  .fn(() =\u003e getUser(2))\n  .withTimeout(2000)\n  .ensure(user =\u003e expect(user).toHaveProperty('active', false));\nconsole.log(inactiveUser);\n```\n\nMotivation\n----------\n\nTesting asynchronous state changes requires waiting for the state change to\noccur. Waiting a fixed amount of time (using `setTimeout` or equivalent) makes\nthe test suite slow and fails to communicate the reason for waiting. UI test\nframeworks often have retry capabilities built in but when testing outside of\nthe browser some other utility is required to reliably wait for asynchronous\nstate changes.\n\n\nFeatures\n--------\n\nThere are many libraries that already solve this problem. This one is unique\nbecause it offers all of the following features:\n\n * **Async** - Retried function can be async.\n * **Config** - Configurable retry timeout.\n * **Fluent** - Retry behavior is configured with fluent builder syntax.\n * **Yields** - Result of retried function is yielded when predicate passes.\n * **Assert** - Retry condition/assertion defined independently of retried function.\n * **Ensure** - Supports negative test cases (ensuring no state change).\n\nAs of the time of writing, the following table describes the feature sets of\nsimilar retry libraries:\n\n| Library                | Async | Config | Yields | Fluent | Assert | Ensure | Notes / Syntax                       |\n| ---------------------- | ----- | ------ | ------ | ------ | ------ | ------ | ------------------------------------ |\n| [Retry-Assert](./)     | Yes   | Yes    | Yes    | Yes    | Yes    | Yes    | `retry(fn).until(() =\u003e assertion)`         |\n| [RSpec-Wait][]         |       | Yes    | Yes    | Yes    | Yes    |        | Ruby                                 |\n| [TryTryAgain][]        | Yes   | Yes    | Yes    |        |        | Yes    | `retry(fn, options)`                 |\n| [Async-Wait-Until][]   | Yes   | Yes    | Yes    |        |        |        | `waitUntil(fn, timeout, delay)`      |\n| [Retry-As-Promised][]  | Yes   | Yes    | Yes    |        |        |        | `retry(fn, options)`                 |\n| [Wait-Until][]         |       | Yes    | Yes    | Yes    |        |        | `waitUntil().times(5).condition(fn)` |\n| [Wait-Until-Promise][] |       | Yes    | Yes    |        |        |        | `waitUntil(fn, timeout, delay)`      |\n| [P-Wait-For][]         | Yes   | Yes    |        |        |        |        | `pWaitFor(condition, options)`       |\n| [Mocha-Retry][]        | Yes   | Yes    |        |        |        |        | Retries entire test                  |\n| [Wait-For-Stuff][]     | Yes   |        |        |        |        |        | `wait.for.predicate(fn)`             |\n\n\nAPI\n---\n\n### Module\n\nThe retry-assert module exports a single function. The following describes its usage\nassuming it is imported as \"retry\".\n\n#### retry()\n\nCreate a new RetryBuilder.\n\nReturns: RetryBuilder\n\n#### retry(fn)\n\nShorthand for `retry().fn(fn)`.\n\nRetuns: RetryBuilder\n\n#### retry.defaultRetryDelay\n\nDefault RetryBuilder retry delay milliseconds (default 200). Modifying only\naffects future RetryBuilder instances.\n\n#### retry.defaultTimeout\n\nDefault RetryBuilder timeout milliseconds (default 1000). Modifying only\naffects future RetryBuilder instances.\n\n### RetryBuilder\n\nA RetryBuilder allows chaining of the retry configuration. It should have\nany number of \"chainable\" configuration methods called followed by a single\n\"terminal\" method invocation. The terminal methods all return a promise\nresolving the latest return value of the retried function.\n\n#### .fn(fn)\n\n*chainable*\n\nSet the function to be retried. The given function may return a Promise. The\nresult of the final invocation of the given function will be resolved by the\npromise returned by this builder's terminal operation.\n\nReturns: RetryBuilder\n\nExample:\n\n    retry()\n      .fn(() =\u003e httpClient.get('/deleted-resource'))\n      .until(response =\u003e expect(response).toHaveProperty('status', 404))\n\n#### .until(fn)\n\n*terminal*\n\nSet the assertion function to apply to the result of each invocation of the\nretried function and begin retrying until the assertion passes or the timeout\nis reached.\n\nAny assertion library can be used as long as a failed assertion throws an\nexception.\n\nRetuns: Promise\n\nThe returned Promise will resolve the last result of the retried function. If\nretrying timed out then the Promise will be rejected.\n\nExample:\n\n    retry(() =\u003e client.getUser(id))\n      .until(user =\u003e expect(user).toHaveProperty('active', true))\n\n#### .ensure(fn)\n\n*terminal*\n\nSet the assertion function to apply to the result of each invocation of the\nretried function and begin retrying until the timeout is reached or until\nassertion fails.\n\nAny assertion library can be used as long as a failed assertion throws an\nexception.\n\nRetuns: Promise\n\nThe returned Promise will resolve the last result of the retried function. If\nan assertion failed then the Promise will be rejected.\n\nExample:\n\n    retry(() =\u003e client.getUser(id))\n      .ensure(user =\u003e expect(user).toHaveProperty('active', false))\n\n#### .untilTruthy()\n\n*terminal*\n\nShorthand for `.untilTruthy(x =\u003e x)`.\n\nRetuns: Promise\n\n#### .untilTruthy(fn)\n\n*terminal*\n\nSet the predicate function to apply to the result of each invocation of the\nretried function and begin retrying until the predicate is truthy or the\ntimeout is reached.\n\nRetuns: Promise\n\nThe returned Promise will resolve the last result of the retried function. If\nretrying timed out then the Promise will be rejected.\n\nExample:\n\n    retry(() =\u003e client.getUser(id))\n      .untilTruthy(user =\u003e user.active)\n\n#### .ensureTruthy()\n\n*terminal*\n\nShorthand for `.ensureTruthy(x =\u003e x)`.\n\nRetuns: Promise\n\n#### .ensureTruthy(fn)\n\n*terminal*\n\nSet the predicate function to apply to the result of each invocation of the\nretried function and begin retrying until the timeout is reached or until\nthe predicate is falsy.\n\nRetuns: Promise\n\nThe returned Promise will resolve the last result of the retried function. If\na predicate was falsy then the Promise will be rejected.\n\nExample:\n\n    retry(() =\u003e client.getUser(id))\n      .ensureTruthy(user =\u003e user.active)\n\n\n#### .withRetryDelay(n)\n\n*chainable*\n\nSet the number of milliseconds between retries (default 200).\n\nReturns: RetryBuilder\n\n\n#### .withTimeout(n)\n\n*chainable*\n\nSet the number of milliseconds before timing out (default 1000).\n\nThis value is use to determine when to stop retrying; it is not used to timeout\nindividual invocations of the retried function. The retried function needs to\nbe responsible for timing out long running operations such as via http client\nconfiguration etc.\n\nThe amount of time passed before timing out is only guaranteed to be *at least*\nthis long. This value is used to approximate the number of retries up-front\nwithout considering the length of time each retry takes. If retries are\nlong-running then the time until timeout occurs may be significantly longer\nthan this value.\n\nReturns: RetryBuilder\n\n\nLegal\n-----\n\nCopyright 2018 Practiv Ltd and Copyright 2023 Nathan Jones. Licensed under the Apache License, Version 2.0.\n\n\n[Mocha-Retry]: https://www.npmjs.com/package/mocha-retry\n[Async-Wait-Until]: https://www.npmjs.com/package/async-wait-until\n[Wait-For-Stuff]: https://www.npmjs.com/package/wait-for-stuff\n[P-Wait-For]: https://www.npmjs.com/package/p-wait-for\n[Retry-As-Promised]: https://www.npmjs.com/package/retry-as-promised\n[TryTryAgain]: https://www.npmjs.com/package/trytryagain\n[Wait-Until-Promise]: https://www.npmjs.com/package/wait-until-promise\n[Wait-Until]: https://www.npmjs.com/package/wait-until\n[RSpec-Wait]: https://github.com/laserlemon/rspec-wait\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fncjones%2Fretry-assert","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fncjones%2Fretry-assert","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fncjones%2Fretry-assert/lists"}