Playwright Request Cache | Cododel
CODODELDEV
EN / RU
Back to Deck
[utility]

Playwright Request Cache

SOURCE TypeScript
VERSION 1.0
AUTHOR Cododel

A PWCache class for Playwright that intercepts network requests and caches responses (CSS, JS, images, fonts) to the filesystem (./.cache). Speeds up tests and reduces network usage by serving cached files.

Source Code

import type { Page, Response } from 'playwright'
import fs from 'fs'
export class PWCache {
private readonly cacheDir = './.cache'
statistics: {
totalRequests: number
totalCached: number
totalNotCached: number
}
constructor(public page: Page) {
this.statistics = new Proxy(
{
totalRequests: 0,
totalCached: 0,
totalNotCached: 0,
},
{
set: (target, key, value) => {
// @ts-ignore
target[key] = value
console.log(`Statistics: ${JSON.stringify(target)}`)
return true
},
},
)
}
async run() {
if (!fs.existsSync(this.cacheDir)) {
fs.mkdirSync(this.cacheDir)
}
await this.page.route('**/*', async (route) => {
this.statistics.totalRequests++
const request = route.request()
const url = request.url()
if (PWCache._isUrlForCaching(url)) {
console.log(`Response Cache: ${url}`)
const cachedFile = `${this.cacheDir}/${encodeURIComponent(url)}`
if (fs.existsSync(cachedFile)) {
this.statistics.totalCached++
const body = fs.readFileSync(cachedFile)
await route.fulfill({
body: body,
headers: { 'content-type': PWCache._getContentType(url) },
})
return
}
} else {
this.statistics.totalNotCached++
}
route.continue()
})
this.page.on('response', async (response: Response) => {
const request = response.request()
const url = request.url()
const cachedFile = `${this.cacheDir}/${encodeURIComponent(url)}`
console.log(`Response: ${url}`)
try {
if (PWCache._isUrlForCaching(url)) {
if (url.includes('.mp4')) {
const file = await response.finished().then(() => response.body())
fs.writeFileSync(cachedFile, file)
} else {
const file = await response.body()
fs.writeFileSync(cachedFile, file)
}
}
} catch (error) {
console.error(`Error Response with cached: ${url}`)
console.info(error)
}
})
}
static _isUrlForCaching(url: string): boolean {
const fileExtensions = [
'.css',
'.js',
'.png',
'.jpg',
'.woff',
'.woff2',
'.ogg',
'.ttf',
'.mp4',
]
switch (true) {
case fileExtensions.some((ext) => url.split('?')[0].endsWith(ext)):
return true
default:
return false
}
}
static _getContentType(url: string): string {
const types: { [key: string]: string } = {
'.css': 'text/css',
'.js': 'application/javascript',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ogg': 'audio/ogg',
'.ttf': 'font/ttf',
}
const ext = url.split('?')[0].split('.').pop()
if (`.${ext}` in types) {
return types[`.${ext}`]
}
return 'application/octet-stream'
}
}
[ ▲ 0 ]