Skip to content

[Question] How can I trigger a service worker update by modifying the HTTP response? #14711

@lemke-ethan

Description

@lemke-ethan

I 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

initial service worker response

Here is the modified service worker response

modified service worker response

stackoverflow post

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions