{"id":14685440,"url":"https://github.com/vitalets/playwright-network-cache","last_synced_at":"2025-05-06T21:07:16.356Z","repository":{"id":244763491,"uuid":"816211122","full_name":"vitalets/playwright-network-cache","owner":"vitalets","description":"Cache and mock network requests in Playwright","archived":false,"fork":false,"pushed_at":"2025-03-02T07:51:29.000Z","size":502,"stargazers_count":58,"open_issues_count":4,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-06T21:07:07.795Z","etag":null,"topics":["cache","network","playwright","playwright-javascript","requests-mock"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vitalets.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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},"funding":{"github":["vitalets"]}},"created_at":"2024-06-17T09:11:47.000Z","updated_at":"2025-05-02T11:35:44.000Z","dependencies_parsed_at":"2024-07-09T14:45:52.807Z","dependency_job_id":"ab458bbe-2f80-4f1a-ade5-c586c627098d","html_url":"https://github.com/vitalets/playwright-network-cache","commit_stats":null,"previous_names":["vitalets/playwright-network-cache"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitalets%2Fplaywright-network-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitalets%2Fplaywright-network-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitalets%2Fplaywright-network-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitalets%2Fplaywright-network-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vitalets","download_url":"https://codeload.github.com/vitalets/playwright-network-cache/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252769420,"owners_count":21801378,"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":["cache","network","playwright","playwright-javascript","requests-mock"],"created_at":"2024-09-12T15:00:44.589Z","updated_at":"2025-05-06T21:07:16.330Z","avatar_url":"https://github.com/vitalets.png","language":"TypeScript","funding_links":["https://github.com/sponsors/vitalets"],"categories":["Recently Updated","Utils"],"sub_categories":["[Sep 15, 2024](/content/2024/09/15/README.md)"],"readme":"# playwright-network-cache\n[![lint](https://github.com/vitalets/playwright-network-cache/actions/workflows/lint.yaml/badge.svg)](https://github.com/vitalets/playwright-network-cache/actions/workflows/lint.yaml)\n[![test](https://github.com/vitalets/playwright-network-cache/actions/workflows/test.yaml/badge.svg)](https://github.com/vitalets/playwright-network-cache/actions/workflows/test.yaml)\n[![npm version](https://img.shields.io/npm/v/playwright-network-cache)](https://www.npmjs.com/package/playwright-network-cache)\n[![license](https://img.shields.io/npm/l/playwright-network-cache)](https://github.com/vitalets/playwright-network-cache/blob/main/LICENSE)\n\nSpeed up [Playwright](https://playwright.dev/) tests by caching network requests on the filesystem.\n\n#### ✨ Features\n\n- Automatically cache network requests during test execution\n- Save responses to the filesystem in a clear, organized structure\n- Modify cached responses dynamically during runtime\n- Reuse cached data across multiple test runs\n- Configure TTL to automatically refresh the cache and keep responses up-to-date\n- View response bodies in a pretty formatted JSON\n- No need for manual mocks management\n- No mess with the HAR format — see [motivation](#motivation)\n\nExample of cache structure:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── GET\n            ├── headers.json\n            └── body.json\n```\n\n## Index\n\u003c!-- doc-gen TOC maxDepth=\"3\" excludeText=\"Index\" --\u003e\n- [Installation](#installation)\n- [Basic usage](#basic-usage)\n- [Examples](#examples)\n  - [Invalidate cache once in a hour](#invalidate-cache-once-in-a-hour)\n  - [Modify cached response](#modify-cached-response)\n  - [Disable cache](#disable-cache)\n  - [Force cache update](#force-cache-update)\n  - [Auto-cache request for all tests](#auto-cache-request-for-all-tests)\n  - [Additional match by HTTP status](#additional-match-by-http-status)\n  - [Additional match by request fields](#additional-match-by-request-fields)\n  - [Split cache by test title](#split-cache-by-test-title)\n  - [Split cache by request URL params](#split-cache-by-request-url-params)\n  - [Split cache by request body](#split-cache-by-request-body)\n  - [Change base dir](#change-base-dir)\n  - [Multi-step cache in complex scenarios](#multi-step-cache-in-complex-scenarios)\n- [API](#api)\n  - [Constructor](#constructor)\n  - [Methods](#methods)\n  - [Options](#options)\n- [Debug](#debug)\n- [Motivation](#motivation)\n- [Alternatives](#alternatives)\n- [Changelog](#changelog)\n- [Feedback](#feedback)\n- [License](#license)\n\u003c!-- end-doc-gen --\u003e\n\n## Installation\nInstall from npm:\n```\nnpm i -D playwright-network-cache\n```\n\n## Basic usage\n\n#### 1. Setup `cacheRoute` fixture\n\nExtend Playwright's `test` instance with  `cacheRoute` fixture:\n```ts\n// fixtures.ts\nimport { test as base } from '@playwright/test';\nimport { CacheRoute } from 'playwright-network-cache';\n\ntype Fixtures = {\n  cacheRoute: CacheRoute;\n};\n\nexport const test = base.extend\u003cFixtures\u003e({\n  cacheRoute: async ({ page }, use) =\u003e {\n    await use(new CacheRoute(page, { /* cache options */ }));\n  },\n});\n```\n\n#### 2. Use `cacheRoute` inside test\nFor example, to cache a GET request to `https://example.com/api/cats`:\n```ts\n// test.ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('https://example.com/api/cats*');\n  // ... perform usual test actions\n});\n```\n\nOn the first run, the test will hit real API and store the response on the filesystem:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── GET\n            ├── headers.json\n            └── body.json\n```\nAll subsequent test runs will re-use cached response and execute much faster. You can invalidate that cache by manually deleting the files. Or provide `ttlMinutes` option to hit real API once in some period of time.\n\nYou can call `cacheRoute.GET|POST|PUT|PATCH|DELETE|ALL` to cache routes with respective HTTP method. Url can contain `*` or `**` to match url segments and query params, see [url pattern](https://playwright.dev/docs/api/class-page#page-route-option-url).\n\nTo catch requests targeting your own app APIs, you can omit hostname in url:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats*');\n  // ...\n});\n```\n\nDefault cache path is: \n```\n{baseDir}/{hostname}/{pathname}/{httpMethod}/{extraDir}/{httpStatus}\n```\n\nSee more examples below or check [configuration options](#options).\n\n## Examples\n\n### Invalidate cache once in a hour\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nTo keep response data up-to-date, you can automatically invalidate cache after configured time period. Set `ttlMinutes` option to desired value in minutes:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats', {\n    ttlMinutes: 60 // hit real API once in a hour\n  });\n  // ...\n});\n```\n\u003c/details\u003e\n\n### Modify cached response\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nYou can modify the cached response by setting the `modify` option to a custom function. In this function, you retrieve the response data, make your changes, and then call [`route.fulfill`](https://playwright.dev/docs/mock#modify-api-responses) with the updated data.\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats', {\n    modify: async (route, response) =\u003e {\n      const json = await response.json();\n      json[0].name = 'Kitty-1';\n      await route.fulfill({ json });\n    }\n  });\n  // ...\n});\n```\nFor modifying JSON responses, there is a helper option `modifyJSON`:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats', {\n    modifyJSON: (json) =\u003e {\n      json[0].name = 'Kitty';\n    },\n  });\n  // ...\n});\n```\n`modifyJSON` can modify response json in-place (like above) or return some result, which will overwrite the original data.\n\u003c/details\u003e\n\n### Disable cache\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nTo disable cache in a **single test**, set `cacheRoute.options.noCache` to `true`:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  cacheRoute.options.noCache = true;\n  await cacheRoute.GET('/api/cats'); // \u003c- this will not cache the request\n  // ...\n});\n```\n\nTo disable cache in **all tests**, set the `noCache` option to `true` in the fixture:\n```ts\nexport const test = base.extend\u003c{ cacheRoute: CacheRoute }\u003e({\n  cacheRoute: async ({ page }, use, testInfo) =\u003e {\n    await use(new CacheRoute(page, {\n      noCache: true\n    }));\n  }\n});\n```\n\n\u003e **Note:** When cache is disabled, `modify` functions still run\n\n\u003c/details\u003e\n\n### Force cache update\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nTo force updating cache files for a **single test**, set `cacheRoute.options.forceUpdate` to `true`:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  cacheRoute.options.forceUpdate = true;\n  await cacheRoute.GET('/api/cats');\n  // ...\n});\n```\n\nTo force updating cache files for **all tests**, set the `forceUpdate` option to `true` in the fixture:\n```ts\nexport const test = base.extend\u003c{ cacheRoute: CacheRoute }\u003e({\n  cacheRoute: async ({ page }, use, testInfo) =\u003e {\n    await use(new CacheRoute(page, {\n      forceUpdate: true\n    }));\n  }\n});\n```\n\u003c/details\u003e\n\n### Auto-cache request for all tests\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nYou can setup caching of some request for all tests. Define `cacheRoute` as [auto fixture](https://playwright.dev/docs/test-fixtures#automatic-fixtures) and setup cached routes:\n```ts\nexport const test = base.extend\u003c{ cacheRoute: CacheRoute }\u003e({\n  cacheRoute: [async ({ page }, use) =\u003e {\n    const cacheRoute = new CacheRoute(page);\n    await cacheRoute.GET('/api/cats');\n    await use(cacheRoute);\n  }, { auto: true }]\n});\n```\n\u003c/details\u003e\n\n### Additional match by HTTP status\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nBy default, only responses with `2xx` status are considered valid and stored in cache.\nTo test error responses, provide additional `httpStatus` option to cache route:\n\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats', { \n    httpStatus: 500 \n  });\n  // ...\n});\n```\nNow error response will be cached in the following structure:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── GET\n            └── 500\n                ├── headers.json\n                └── body.json\n```\n\u003c/details\u003e\n\n### Additional match by request fields\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nBy default, requests are matched by:\n```\nHTTP method + URL pattern + (optionally) HTTP status \n```\nIf you need to match by other request fields, provide custom function to `match` option. \nExample of matching GET requests with query param `/api/cats?foo=bar`:\n\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats*', { \n    match: req =\u003e new URL(req.url()).searchParams.get('foo') === 'bar'\n  });\n  // ...\n});\n```\n\n\u003e Notice `*` in `/api/cats*` to match query params\n\n\u003c/details\u003e\n\n### Split cache by test title\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nBy default, cached responses are stored in a shared directory and re-used across tests.\nIf you want to isolate cache files for a particular test, utilize `cacheRoute.options.extraDir` - an array of extra directories to be inserted into the cache path:\n\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  cacheRoute.options.extraDir.push('custom-test');\n  await cacheRoute.GET('/api/cats');\n  // ...\n});\n```\nGenerated cache structure:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── GET\n            └── custom-test       # \u003c- extra directory\n                ├── headers.json\n                └── body.json\n```\nYou can freely transform `extraDir` during the test and create nested directories if needed.\n\nTo automatically store cache files in a separate directories for **each test**, \nyou can set `extraDir` option in a fixture setup:\n```ts\nexport const test = base.extend\u003c{ cacheRoute: CacheRoute }\u003e({\n  cacheRoute: async ({ page }, use, testInfo) =\u003e {\n    await use(new CacheRoute(page, {\n      extraDir: testInfo.title  // \u003c- use testInfo.title as a unique extraDir\n    }));\n  }\n});\n```\nAfter running two tests with titles `custom test 1` and `custom test 2`,\nthe generated structure is:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── GET\n            ├── custom-test-1\n            │   ├── headers.json\n            │   └── body.json\n            └── custom-test-2\n                ├── headers.json\n                └── body.json           \n```\n\u003c/details\u003e\n\n### Split cache by request URL params\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nTo split cache by request query params, you can set `extraDir` to a function. It accepts `request` as a first argument and gives access to any prop of the request:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats*', {\n    extraDir: req =\u003e new URL(req.url()).searchParams.toString()\n  });\n  // ...\n});\n```\n\n\u003e Notice `*` in `/api/cats*` to match query params\n\nGiven the following requests:\n```\nGET /api/cats?foo=1\nGET /api/cats?foo=2\n```\nCache structure will be:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── GET\n            ├── foo=1\n            │   ├── headers.json\n            │   └── body.json\n            └── foo=2\n                ├── headers.json\n                └── body.json\n```\n\u003c/details\u003e\n\n### Split cache by request body\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nTo split cache by request body, you can set `extraDir` to a function. It accepts `request` as a first argument and gives access to any prop of the request:\n```ts\ntest('test', async ({ page, cacheRoute }) =\u003e {\n  await cacheRoute.GET('/api/cats', {\n    extraDir: req =\u003e req.postDataJSON().email\n  });\n  // ...\n});\n```\nHaving the following requests:\n```\nPOST -d '{\"email\":\"user1@example.com\"}' /api/cats\nPOST -d '{\"email\":\"user2@example.com\"}' /api/cats\n```\nCache structure will be:\n```\n.network-cache\n└── example.com\n    └── api-cats\n        └── POST\n            ├── user1@example.com\n            │   ├── headers.json\n            │   └── body.json\n            └── user2@example.com\n                ├── headers.json\n                └── body.json\n```\n\u003c/details\u003e\n\n### Change base dir\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nBy default, cache files are stored in `.network-cache` base directory. To change this location, set `baseDir` option:\n\n```ts\nexport const test = base.extend\u003c{ cacheRoute: CacheRoute }\u003e({\n  cacheRoute: async ({ page }, use, testInfo) =\u003e {\n    await use(new CacheRoute(page, {\n      baseDir: `test/.network-cache`\n    }));\n  }\n});\n```\nMoreover, you can set separate `baseDir` for each Playwright project or each test:\n```ts\nexport const test = base.extend\u003c{ cacheRoute: CacheRoute }\u003e({\n  cacheRoute: async ({ page }, use, testInfo) =\u003e {\n    await use(new CacheRoute(page, {\n      baseDir: `test/.network-cache/${testInfo.project.name}`\n    }));\n  }\n});\n```\nExample of generated structure\n```\n.network-cache\n├── project-one\n│   └── example.com\n│       └── api-cats\n│           └── GET\n│               ├── headers.json\n│               └── body.json\n└── project-two\n    └── example.com\n        └── api-cats\n            └── GET\n                ├── headers.json\n                └── body.json\n\n```\n\n\u003e In that example, you get more isolation, but less cache re-use. It's a trade-off, as always 🤷‍♂️\n\n\u003c/details\u003e\n\n### Multi-step cache in complex scenarios\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\nFor complex scenarios, you may want to have different cached responses for the same API. Example: adding a new todo item into the todo list.\n\nWith caching in mind, the plan for such test can be the following:\n\n1. Set cache for GET request to load original todo items\n2. Open the todo items page\n3. Set cache for POST request to create new todo item\n4. Set cache for GET request to load updated todo items\n5. Enter todo text and click \"Add\" button\n6. Assert todo list is updated\n\nThe implementation utilizes `extraDir` option to dynamically change cache path in the test: \n\n```ts\ntest('adding todo', async ({ page, cacheRoute }) =\u003e {\n  // set cache for GET request to load todo items\n  await cacheRoute.GET('/api/todo');\n\n  // ...load page\n\n  // CHECKPOINT: change cache dir, all subsequent requests will be cached in `after-add` dir\n  cacheRoute.options.extraDir.push('after-add');\n\n  // set cache for POST request to create a todo item\n  await cacheRoute.POST('/api/todo');\n  \n  // ...add todo item\n  // ...reload page\n  // ...assert todo list is updated\n});\n```\nGenerated cache structure:\n```\n.network-cache\n└── example.com\n    └── api-todo\n        ├── GET\n        │   ├── headers.json\n        │   ├── body.json\n        │   └── after-add\n        │       ├── headers.json\n        │       └── body.json\n        └── POST\n            └── after-add\n                ├── headers.json\n                └── body.json\n```\n\n\u003e You may still modify cached responses to match test expectation. But it's better to make it as *replacement* modifications, not changing the structure of the response body. Keeping response structure unchanged is more \"end-2-end\" approach.\n\n\u003c/details\u003e\n\n## API\nThe `CacheRoute` class manages caching of routes for a Playwright `Page` or `BrowserContext`. It simplifies setting up HTTP method handlers for specific routes with caching options.\n\n### Constructor\n\n```ts\nconst cacheRoute = new CacheRoute(page, options?)\n```\n\n- **page**: The Playwright `Page` or `BrowserContext` to manage routes.\n- **options**: Optional configuration to control caching behavior.\n\n### Methods\n\nThese methods enable caching for specific HTTP routes:\n\n- `cacheRoute.GET(url, optionsOrFn?)`\n- `cacheRoute.POST(url, optionsOrFn?)`\n- `cacheRoute.PUT(url, optionsOrFn?)`\n- `cacheRoute.PATCH(url, optionsOrFn?)`\n- `cacheRoute.DELETE(url, optionsOrFn?)`\n- `cacheRoute.HEAD(url, optionsOrFn?)`\n- `cacheRoute.ALL(url, optionsOrFn?)`\n\n#### Params\n- **url**: [Url pattern](https://playwright.dev/docs/api/class-page#page-route-option-url)\n- **optionsOrFn**: Caching options or a function to modify the response\n\n### Options\nYou can provide options to `CacheRoute` constructor or modify them dynamically via `cacheRoute.options`. All values are optional.\n\n#### baseDir\n`string`\n\nBase directory for cache files.\n\n#### extraDir\n`string | string[] | ((req: Request) =\u003e string | string[])`\n\nAdditional directory for cache files. Can be a string, array of strings, or a function that accepts a request and returns a string or an array of strings.\n\n#### match\n`(req: Request) =\u003e boolean`\n\nFunction to add additional matching logic for requests. Returns `true` to cache, or `false` to skip.\n\n#### httpStatus\n`number`\n\nCache responses with the specified HTTP status code.\n\n#### ttlMinutes\n`number`\n\nTime to live for cached responses, in minutes.\n\n#### overrides\n`RequestOverrides | ((req: Request) =\u003e RequestOverrides)`\n\nObject or function that provides request [overrides](https://playwright.dev/docs/api/class-route#route-fetch) (e.g., headers, body) when making real calls.\n\n#### modify\n`(route: Route, response: APIResponse) =\u003e Promise\u003cunknown\u003e`\n\nFunction to modify the response before caching. This is called for each route.\n\n#### modifyJSON\n`(json: any) =\u003e any`\n\nHelper function to modify JSON responses before caching.\n\n#### noCache\n`boolean`\n\nIf `true`, disables caching and always makes requests to the server.\n\n#### forceUpdate\n`boolean`\n\nIf `true`, always requests from the server and updates the cached files.\n\n#### buildCacheDir\n`(ctx: BuildCacheDirArg) =\u003e string[]`\n\nFunction to build a custom cache directory, providing fine-grained control over the cache file location.\n[Default implementation](https://github.com/vitalets/playwright-network-cache/blob/main/src/CacheRoute/defaults.ts).\n\n## Debug\nTo debug caching, run Playwright with the following `DEBUG` environment variable:\n```bash\nDEBUG=playwright-network-cache npx playwright test\n```\n\n## Motivation\nPlaywright has built-in [support for HAR format](https://playwright.dev/docs/mock#mocking-with-har-files) to record and replay network requests. \nBut when you need more fine-grained control of network, it becomes messy. Check out these issues where people struggle with HAR: \n\n- [#21405](https://github.com/microsoft/playwright/issues/21405)\n- [#30754](https://github.com/microsoft/playwright/issues/30754)\n- [#29190](https://github.com/microsoft/playwright/issues/29190)\n\nThis library intentionally does not use HAR. Instead, it generates file-based cache structure, giving you full control of what and how is cached.\n\n## Alternatives\nAlternatively, you can check the following packages:\n* [playwright-intercept](https://github.com/alectrocute/playwright-intercept) - uses Cypress-influenced API\n* [playwright-advanced-har](https://github.com/NoamGaash/playwright-advanced-har) - uses HAR format\n* [playwright-request-mocker](https://github.com/kousenlsn/playwright-request-mocker) uses HAR format, looks abandoned\n\n## Changelog\nSee [CHANGELOG.md](./CHANGELOG.md).\n\n## Feedback\nFeel free to share your feedback and suggestions in [issues](https://github.com/vitalets/playwright-network-cache/issues).\n\n## License\n[MIT](https://github.com/vitalets/playwright-network-cache/blob/main/LICENSE)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitalets%2Fplaywright-network-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvitalets%2Fplaywright-network-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitalets%2Fplaywright-network-cache/lists"}