[Question] How can I trigger a service worker update by modifying the HTTP response?
See original GitHub issueI am working on an SPA that I recently added a service worker to, which pre-caches the application shell using a cache-first strategy and caches navigation requests using a network -first strategy.
The SPA contains some logic that displays a “A new version is available!” banner with a “Reload” button at the top of the page when it detects a “waiting” service worker. I would like to write an end-to-end test for this logic and the logic behind the “Reload” button but cannot figure out how to trigger a service worker update in Playwright.
The approach that I have been playing with is modifying the service worker response after the service worker has been installed and a page reload has been triggered. I can modify the response and its response headers but it is not triggering an update event in the browser.
Here is the test code:
test("shows an update button when the service worker file has changed", async ({
page,
context
}, testInfo) => {
const newScriptBytes = "/*test*/"
const timeout = testInfo.timeout
await page.goto("/")
await waitForReadyServiceWorker({ context, page, timeout })
// change the bytes of all additional service worker requests
await page.route("/service-worker.js", async route => {
const serverResponse = await page.request.fetch(serviceWorkerUrl, {
ignoreHTTPSErrors: true
})
let serviceWorkerScript = await serverResponse.text()
serviceWorkerScript = serviceWorkerScript.concat(newScriptBytes)
await route.fulfill({
response: serverResponse,
body: serviceWorkerScript,
headers: {
...serverResponse.headers(),
"Etag": "v1",
"Last-Modified": new Date().toUTCString(),
"Content-Length": serviceWorkerScript.length.toString()
}
})
})
await page.reload()
await expect(
page.locator(`text=A new version is available!`)
).toBeVisible()
})
export async function waitForReadyServiceWorker(args: {
context: BrowserContext
page: Page
timeout?: number
}): Promise<Worker | undefined> {
const timeoutId = createOperationTimeout(args.timeout)
const url = await args.page.evaluate(async () => {
const readySw = await navigator.serviceWorker.ready
return readySw.active.scriptURL
})
clearTimeout(timeoutId)
return args.context.serviceWorkers().find(worker => worker.url() === url)
}
Here is the initial service worker response
Here is the modified service worker response
Issue Analytics
- State:
- Created a year ago
- Comments:8 (7 by maintainers)
Intercepting the service-worker.js request is not yet implemented in Playwright. We are almost there, but it needs some final touches. I’ll let @rwoll comment on when it becomes available.
The rest of your test seems to be ok. You’ll reload the page, it’ll re-request service worker, then sw will load and your page will be able to detect a new sw. You could also do something like
reg.update
here that would theoretically check for the new sw w/o reloading. It requires you to modify the SPA though to store registration pointer, but avoids artificial reload. Does this all make sense?Yes, this set of changes does intercept the main request as well!