{"id":18313995,"url":"https://github.com/oreillymedia/cypress-playback","last_synced_at":"2025-05-02T12:31:24.992Z","repository":{"id":37051366,"uuid":"451951328","full_name":"oreillymedia/cypress-playback","owner":"oreillymedia","description":"Automatically record and playback HTTP requests in Cypress.","archived":false,"fork":false,"pushed_at":"2024-12-10T22:09:19.000Z","size":2922,"stargazers_count":12,"open_issues_count":9,"forks_count":3,"subscribers_count":38,"default_branch":"main","last_synced_at":"2024-12-13T09:09:37.037Z","etag":null,"topics":["cypress","cypress-plugin","fixtures"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oreillymedia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.MD","code_of_conduct":"CODE-OF-CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-25T16:19:31.000Z","updated_at":"2024-12-10T22:09:22.000Z","dependencies_parsed_at":"2023-02-04T10:45:29.471Z","dependency_job_id":"66e0d1d7-d58c-457b-b39a-16804dc6913b","html_url":"https://github.com/oreillymedia/cypress-playback","commit_stats":{"total_commits":81,"total_committers":10,"mean_commits":8.1,"dds":0.6790123456790124,"last_synced_commit":"383aff967a6e04d0e5ad7b3019b6afad69d8712f"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oreillymedia%2Fcypress-playback","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oreillymedia%2Fcypress-playback/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oreillymedia%2Fcypress-playback/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oreillymedia%2Fcypress-playback/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oreillymedia","download_url":"https://codeload.github.com/oreillymedia/cypress-playback/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252038152,"owners_count":21684632,"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","cypress-plugin","fixtures"],"created_at":"2024-11-05T16:29:27.529Z","updated_at":"2025-05-02T12:31:24.983Z","avatar_url":"https://github.com/oreillymedia.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cypress Playback\n\n## 🚨 This library is no longer maintained. 🚨\nSince the creation of this library, the Mock Service Worker API has seen some\nsignificant updates and support and it currently provides, in our opinion, \na more flexible and better solution to what this library was created to solve.\n\n**We recommend using Mock Service Worker (MSW) instead of this plugin. \nMSW solves a similar problem but is more flexible and can be used in a variety\nof environments including Cypress, Node.js and Jest. For more information,\nsee the [MSW documentation][11].**\n\n---\n\n\u003e :arrows_counterclockwise: **_Automatically record and playback HTTP requests made in\n\u003e Cypress tests._**\n\n[![NPM](https://img.shields.io/npm/v/@oreillymedia/cypress-playback)][9]\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)][8]\n\nCypress Playback is a plugin and a set of commands that allows [Cypress][7] to\nautomatically record responses to network requests made during a test run. These\nresponses are then saved to disk and made available for playback in later test\nruns. This allows for applications or components under test to always receive\nthe same response to a request, no matter where or when they run.\n\nThis plugin is *not* a replacement for the `cy.intercept` command, but is\ninstead a wrapper around that command. It handles situations where a developer\nisn't concerned with the contents of a response to a network request, just that\nthe response is always the same.\n\n## Quick start\n\nThis README contains the full documentation for Cypress Playback\nbut this quick start will get you going in just a few minutes.\n\n### Installation\n\n**Step 1.** In a project with Cypress installed, run:\n\n```bash\nnpm install @oreillymedia/cypress-playback -D\n```\n\n**Step 2.** Add the tasks to the project's `setupNodeEvents` in `cypress.config.js` file\n([Cypress plugin usage docs][10]):\n\n```JavaScript\nsetupNodeEvents(on, config) =\u003e {\n  require('@oreillymedia/cypress-playback/addTasks')(on, config);\n  ...\n})\n```\n\n**Step 3.** Add the commands to the project's `cypress/support/commands.js` file:\n\n```JavaScript\nimport '@oreillymedia/cypress-playback/addCommands';\n```\n\n### Insert `playback` commands\n\nThe `playback` command is a wrapper around the [Cypress' `intercept`][1] command\nand can be used in much the same way.\n\nA notable exception is that it doesn't provide any way to attach a request\nhandler or a provide a static response, as that isn't the purpose of this\nplugin. This plugin is designed to capture real responses and record them for\nlater playback. For providing fixtures as responses, the normal `cy.intercept`\ncommand should be used.\n\n#### Syntax\n\n```JavaScript\n// Records or plays back network responses, depending on the value of\n// `PLAYBACK_MODE` in the Cypress environment.\ncy.playback(method, url, playbackOptions);\ncy.playback(method, routeMatcher, playbackOptions);\n```\n\n#### Usage\n```JavaScript\n// Capturing a request.\ncy.playback('POST', \\/users\\/);\n// Providing playback options.\ncy.playback('GET', \\/todos\\/, { toBeCalledAtLeast: 2 });\n// Aliasing the request for later use in the test.\ncy.playback('GET', 'https://www.example.com/300/150').as('image');\n```\n\n### Run Cypress\n\nRun Cypress as you normally would using `cypress open`. By default using\n`cypress open` will operate Cypress playback in _hybrid_ mode,\nmeaning it will save any requests that have not already been saved and\nfulfill ones that have and as such, is a great place to start.\n\nThat's it! Your specified `playback` URL calls are now stored as static\nfixtures and will be automatically fulfilled in further test runs.\n\nKeep reading for further info on the various [playback modes](#playback-mode)\nand [playback API](#the-playback-command).\n\n---\n\n## API and usage documentation\n\n### The `playback` command\n\n#### Arguments\n\n##### **method (string)**\n\nCapture requests using this specific HTTP method (`GET`, `POST`, etc).\n\n\u003e 🚨 **NOTE:** Unlike the `intercept` command, the command requires a `method`\n\u003e argument.\n\n##### **url (string, glob, RegExp)**\n\nCapture requests that match this value. See the `intercept` command's [\"Matching\nurl\"][2] documentation for more details.\n\n##### **routeMatcher (RouteMatcher)**\n\nAn object used to define a request that can be captured. See the `intercept`\ncommand's [\"RouteMatcher\"][3] documentation for more details.\n\n##### **playbackOptions (PlaybackOptions)**\n\n`playbackOptions` is an object used to modify the behavior of the `playback`\ncommand. The example object below is showing the default values for all\navailable properties.\n\n```JavaScript\n{\n  allowAllStatusCodes: false,\n  toBeCalledAtLeast: 1,\n  matching: {\n    ignores: {\n      attributes: [],\n      bodyProperties: [],\n      searchParams: []\n    }\n  },\n  rewriteOrigin: undefined\n}\n```\n\nMore detailed examples of how to use these properties can be found in the\n[\"Requests and Responses\"][5] section below.\n\n###### **playbackOptions.allowAllStatusCodes (boolean)**\n\nBy default, the command will only record responses that have a `2xx` status\ncode. By setting this to `true`, all responses will be recorded.\n\nNote that trying to record `3xx` responses will lead to some strange behavior\nand is area where more work is needed in the plugin.\n\n*Default:* `false`\n\n###### **playbackOptions.toBeCalledAtLeast (number)**\n\nThe minimum number of times the system under test must trigger a network request\nthat matches the `url` or `routeMatcher`. See [\"All Requests Complete\"\nAssertions][6] for more details.\n\n*Default:* `1` - At least 1 request must have been matched.\n\n###### **playbackOptions.matching (object)**\n\nThis object modifies how the command tries to match recorded responses to a\nnetwork request.\n\n*Default:* `undefined`\n\n###### **playbackOptions.matching.ignores (string[] | object)**\n\nThis property supports two different value types:\n\n* **`string[]`:** An array of network request attributes to ignore. See the\n  `attributes` entry below.\n\n* **`object:`** An object consisting of one or more of the following properties:\n\n  * **`attributes`:** A string array consisting of one or more of the following\n    values.\n    * `protocol`\n    * `hostname`\n    * `port`\n    * `pathname`\n    * `search`\n    * `method`\n    * `body`\n\n  * **`bodyProperties`:** A string array consisting of properties in the network\n    request body to ignore. This assumes that the body is a JSON object.\n\n  * **`searchParams`:** A string array consisting of search parameters on the\n    network request url to ignore.\n\n###### **playbackOptions.rewriteOrigin (string)**\n\nThis string will be used in place of the current origin found in the network\nrequest's URL.\n\n*Default:* `undefined`\n\n#### Yields\n\nThe command yields the response of the `cy.intercept` command it is wrapping.\nSee the `intercept` command's [\"Yields\"][4] section for more details.\n\n### Requests and Responses\n\nSince the words \"request\" and \"response\" can have a few meanings, it's helpful\nto provide a few definitions first:\n\n* **Request**: A network request made by the browser that may be intercepted by\n  a request matcher.\n* **Response**: The response sent back to the browser's network request. The\n  plugin saves responses to the automatically created fixture file.\n* **Request Matcher:** This is what is being created by calling the\n  `cy.playback` command. Request matchers are saved to the automatically created\n  fixtures file. Depending on how the route matching is setup in the matcher,\n  the plugin may record multiple responses for a single matcher.\n\n\u003e 🚨 **A Warning on Secrets:** A response that is recorded in the fixtures file,\n\u003e which will likely be committed to the project's repository, will contain both\n\u003e the request's and response's headers and body. While this file is binary and\n\u003e is compressed, it isn't encrypted or protected in any way. When recording\n\u003e responses, make sure there aren't any secrets used by a request or returned in\n\u003e a response that you wouldn't want exposed.\n\n#### Request Matching versus Response Matching\n\nSince a developer can create a request matcher that can potentially match\nmultiple requests, it's important that the plugin know how to return the right\nresponse to any individual request. That means the plugin is performing both\nrequest matching and response matching.\n\nTo explain this better, consider the examples below.\n\n#### Example 1: Multiple Requests to the Same Request Matcher\n\nThe developer wants to record all requests made to `/api/v1/todo/`. Their app\nwill be making multiple calls to this Api, each with an id value appended to the\nend. The developer sets up the following `playback` command in their test:\n\n```JavaScript\ncy.playback('GET', new RegExp('/api/v1/todo/'));\n```\n\nInternally, the plugin is calling `cy.intercept` with that RegExp as the route\nmatcher. The app, over the course of the test, makes requests to\n`/api/v1/todo/1` through `/api/v1/todo/5`. The plugin's wrapped `intercept`\ncommand intercepts all of those requests and its custom request handler is\ncalled with the full details for each request that is being made. For example,\nthat means the request handler will see information such as a request to\n`https://example.com/api/v1/todo/1` was made.\n\nWhat happens next depends on what mode the plugin is in:\n\n* If the plugin is in \"record\" mode, it will capture the response and write it\n  to disk when the test is completed.\n* If the plugin is in \"playback\" mode, it will try to find a previously recorded\n  response for that specific request.\n\nMore details on the plugin's mode can be found below.\n\n##### Example 2: Differences between Test Environments\n\nThe developer is recording requests made to their local instance, which is\nhosted at `http://localhost:4200`. As it runs, the app will be making requests\nto `http://localhost:4200/api/v1/todo/`. However, in the project's CI job, the\ntests will be run in a Docker container, so the app will be hosted at\n`http://test:8000`.\n\nThis is a problem, because by default the plugin expects every attribute of the\nrequest to match. The attributes that must match are:\n\n* `protocol`\n* `hostname`\n* `port`\n* `pathname`\n* `search`\n* `method`\n* `body`\n\nIf any of those are different, the recorded response won't be returned. However,\nthe `matching.ignores` property allows the developer to specifically say certain\nattributes shouldn't be considered.\n\n\u003e ℹ️ Note that while headers are recorded and played back, they are not used\n\u003e when trying to look up a matching response. This is because headers tend to\n\u003e vary considerably, so they are always ignored. However, functionality could be\n\u003e added to the plugin to allow a developer to specify which headers to include\n\u003e when matching.\n\nIn this case, since the `hostname` and `port` are going to be different, the\ndeveloper should write their `playback` command like this:\n\n```JavaScript\ncy.playback('GET', new RegExp('/api/v1/todo/'), {\n  matching: { ignores: ['hostname', 'port'] }\n});\n```\n\nOf course, there is a danger that if too many attributes of a request are\nignored, the plugin won't find the correct response. For example, if every\nrequest attribute were ignored, then every recorded response would be a match.\nIt's best to limit the list of ignored attributes to smallest number possible.\n\n##### Example 3: Ignoring Dynamic Values in a Network Request\n\nThe application under test is making a `POST` request to an endpoint. The body\nof this network request contains a timestamp:\n\n```JSON\n{\n  \"when\": {\n    \"timestamp\": \"2022-02-01T14:43:10.023Z\"\n  }\n}\n```\n\nIn addition, because the backend developer was malicious, the url for the\nnetwork request must also contain a search parameter that includes the current\ndate:\n\n```\nhttps://example.com/api/v1/access?current_date=2022-02-01\n```\n\nIn this case, though, the developer can tell the playback command to ignore both\nthat property and the search parameter when trying to find a matching response:\n\n```JavaScript\ncy.playback('POST', new RegExp('/api/v1/access'), {\n  matching: {\n    ignores: {\n      bodyProperties: ['when.timestamp'],\n      searchParams: ['current_date']\n    }\n  }\n});\n```\n\n###### Body Property Paths\n\nAs seen above, the values in the `bodyProperties` array are strings defining\nwhere the property can be found in the object. The example below provides\nexamples of supported paths.\n\n```JavaScript\nconst example = {\n  foo: \"value\",\n  bar: {\n    baz: \"value\",\n    qux: [\n      {\n        'Some whitespace': {\n          quux: \"value\"\n        }\n      }\n    ]\n  }\n};\n\n// Paths to some of the properties above.\nconst bodyProperties = [\n  'foo',`\n  'bar.baz',`\n  'bar.qux.0[\"Some whitespace\"].quux',`\n];\n```\n\nNote that arrays are supported, but the indices are not surrounded by brackets.\nIn addition, there is currently no concept of a wildcard that would cause a\nproperty to be removed from all object entries in an array.\n\n##### Example 5: Only One Response is Expected\n\nThe application under test is only making a certain network request once during\nthe test run. In addition, the developer is not concerned with any dynamic\nvalues that may be found in the request. In such a case, the `matching.anyOnce`\nproperty can be used:\n\n```JavaScript\ncy.playback('POST', new RegExp('/api/v1/access'), {\n  matching: { anyOnce: true }\n});\n```\n\nAs its name implies, the `anyOnce` property expects there to only be one\nresponse recorded for a request matcher. During playback, it expects only a\nsingle network request will match that request matcher and, in that case, it\nprovides the recorded response. If it ever tries to handle more than one network\nrequest, an error is thrown an the test will fail.\n\nThe advantage of this property is that it can greatly simplify setting up\nresponse matching. In these cases, the developer doesn't have to pull apart the\nrequest and provide the dynamic elements to the `playback` command.\n\n### Playback Mode\n\nThe plugin can be run in one of three different modes:\n\n* `playback` - Previously recorded responses are played back. If a matching\n  response is not found for the intercepted request, an error is thrown and the\n  test will fail. This is the default mode when Cypress is started with the\n  `run` option.\n* `record` - All request responses are recorded and any previously recorded\n  responses are ignored.\n* `hybrid` - If a previously recorded response matches the intercepted request,\n  the plugin plays back that response. Otherwise, the response is recorded when\n  the request completes. This is the default mode when Cypress is started with\n  the `open` option.\n\nThe mode can be overridden through an environment variable:\n\n```bash\nCYPRESS_PLAYBACK_MODE=record npx cypress open\n```\n\nIt can also be set in the `cypress.json`:\n\n```json\n{\n  \"env\": {\n    \"PLAYBACK_MODE\": \"record\"\n  }\n}\n```\n\n### \"All Requests Complete\" Assertion\n\nDuring the `afterEach` stage of a test, the plugin will assert that all request\nmatchers were matched a minimum number of times. The default number of times is\n`1`, but that value can be changed through the `toBeCalledAtLeast` option.\n\n```JavaScript\n// This request matcher is considered optional, so don't fail the test. There\n// is a danger in this, though. See the \"Why This is Important\" section below.\ncy.playback('GET', new RegExp('/api/v1/todo/'), { toBeCalledAtLeast: 0 });\n// This request matcher must be matched 5 times, or the test will fail.\ncy.playback('GET', new RegExp('/api/v1/todo/'), { toBeCalledAtLeast: 5 });\n```\n\nWhat the plugin is doing during the `afterEach` stage is, over a period of time,\nchecking to see if any request matchers still have not been matched their\nminimum number of times. If after 10 seconds, the expected number of requests is\nstill not met, the plugin fails the test.\n\n#### Why This is Important\n\nThere are two factors that make this assertion important. One factor is that\nCypress may consider a test complete before all the network requests made by the\nsystem under test are received. The other factor is that the order in which\nnetwork requests complete is non-deterministic. This means that during one test\nrun one request may complete before Cypress considers the test does, while in\nanother run that same request has not.\n\nTo handle that, the plugin tries to make sure every `playback` command has\nreceived at least one response that can be recorded. This is to ensure that all\nrequest matchers have responses available when the test is run in \"playback\"\nmode.\n\nAll of this is to explain why an \"optional\" request matcher can be dangerous.\nSetting `toBeCalledAtLeast` to `0` tells the plugin that not receiving a request\nmatching that `playback` command is fine. When the plugin is running in \"record\"\nor \"hybrid\" mode, the test will not be failed because of this. However, when the\nplugin is running in \"playback\" mode and a request is made that matches that\n`playback` command, the plugin returns a `404`. That may result in a flaky test\nthat can be hard to debug.\n\n#### \"Stale\" Request Matchers and Responses\n\nAs applications and tests change over time, requests that were once made may not\nbe in the future. To keep these old request matchers and responses from building\nup in the recorded fixtures file, the plugin considers any request matcher or\nresponse loaded from disk as \"stale\". These stale entities are automatically\nremoved when the fixtures file is written to disk.\n\nA \"stale\" entity becomes \"fresh\" when...\n\n* Request matchers are considered \"fresh\" if a `cy.playback` command is invoked\n  in the test that matches their `method`, `url` or `routeMatcher`, and playback\n  options.\n* Responses are considered \"fresh\" if a single request is made that exactly\n  matches the request attributes they match on. Meaning, a request was made that\n  matches the `post`, `hostname`, etc. they match on.\n\n### The Recorded Fixtures File\n\nAll the captured responses for a test are grouped together into a single\n`.cy-playbacks` file that is saved to the Cypress fixtures folder. A subdirectory\nis created in the fixtures folder for each spec file. Within that folder, a\nfixture file is created for each test in the spec file. The file name is a\ncombination of the test's name and any `describe` blocks it is nested under.\n\nConsider the following example spec file.\n\n* **File Name:** `./cypress/integration/app/basic.spec.js`\n* **Spec File Contents:**\n  ```JavaScript\n  describe('app', () =\u003e {\n    it('works', () =\u003e {\n      // Test code.\n    });\n    it('still works', () =\u003e {\n      // Test code.\n    });\n    describe('another language', () =\u003e {\n      it('works', () =\u003e {\n        // Test code.\n      });\n    });\n  });\n  ```\n* **Generated Fixture Files:**\n  ```bash\n  ./cypress/fixtures/app/basic-spec/app-works.cy-playbacks\n  ./cypress/fixtures/app/basic-spec/app-still-works.cy-playbacks\n  ./cypress/fixtures/app/basic-spec/app-another-language-works.cy-playbacks\n  ```\n\n\u003e ⚠️ As can seen, if the location of the spec file or the structure or name of\n\u003e the test changes, the generated file location and name will change as well.\n\u003e This means your project could end up with orphaned fixture files, as the\n\u003e plugin doesn't keep track of changes made to file names.\n\n#### File Format\n\nThe `.cy-playbacks` file is a binary file, which is created by JSON stringifying\nthe request matchers and requests and compressing that output with Node's\n`zlib.deflate` function. There are two reasons for this approach:\n\n* As a compressed binary file it will take up less space on disk and Git LFS can\n  be used to store them.\n* The unreadable nature of the file prevents developers from easily being able\n  to edit them. This is important, because the plugin overwrites the fixture\n  file whenever a test completes, so any manual edits would be immediately lost.\n\n## The `isPlayingBackRequests` command\n\nThis command can be used to allow conditional logic in a test when the plugin\nwill playback recorded requests.\n\n### Syntax\n\n```JavaScript\ncy.isPlayingBackRequests();\n```\n\n### Usage\n\n```JavaScript\ncy.isPlayingBackRequests().then((isPlayingBack) =\u003e {\n  cy.log(`isPlayingBack: ${isPlayingBack}`);\n});\n```\n\n### Arguments\n\nNone\n\n### Yields\n\n* `cy.isPlayingBackRequests` yields `true` if the playback mode is `playback` or\n  `hybrid.` Otherwise, yields `false`.\n\n## The `isRecordingRequests` command\n\nThis command can be used to allow conditional logic in a test when the playback\nmode is set to `record`. For example, this could be used to perform a login step\nthat wouldn't be needed in playback mode.\n\n### Syntax\n\n```JavaScript\ncy.isRecordingRequests();\n```\n\n### Usage\n\n```JavaScript\ncy.isRecordingRequests().then((isRecording) =\u003e {\n  cy.log(`isRecording: ${isRecording}`);\n});\n```\n\n### Arguments\n\nNone\n\n### Yields\n\n* `cy.isRecordingRequests` yields `true` if the playback mode is `record` or\n  `hybrid.` Otherwise, yields `false`.\n\n[1]:https://docs.cypress.io/api/commands/intercept\n[2]:https://docs.cypress.io/api/commands/intercept#Matching-url\n[3]:https://docs.cypress.io/api/commands/intercept#routeMatcher-RouteMatcher\n[4]:https://docs.cypress.io/api/commands/intercept#Yields\n[5]:#requests-and-responses\n[6]:#all-requests-complete-assertion\n[7]:https://github.com/cypress-io/cypress\n[8]:code-of-conduct.md\n[9]:https://www.npmjs.com/package/@oreillymedia/cypress-playback\n[10]:https://docs.cypress.io/guides/tooling/plugins-guide#Using-a-plugin\n[11]:https://mswjs.io/docs/comparison#cypress---cyintercept\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foreillymedia%2Fcypress-playback","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foreillymedia%2Fcypress-playback","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foreillymedia%2Fcypress-playback/lists"}