Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
lemke-ethan opened this issue Jun 8, 2022 · 9 comments

Comments

@lemke-ethan
Copy link

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

@pavelfeldman
Copy link
Member

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?

@lemke-ethan
Copy link
Author

@pavelfeldman thank you for the reply!

I am a little confused by your links regarding the service-worker.js script response modification. It seems like that PR is adding the ability to intercept requests that a service worker makes, which I am really excited about and plan to use in the future. However, I don't see how it has anything to do with intercepting and modifying the response for the service-worker.js script request, which, in my case, would allow me to emulate a service-worker.js script change on the server so I can trigger a service worker "update" in the client. I can see that my service-worker.js response headers and body have changed, but don't get why the browser won't attempt to install that new service worker script. Do you (or @rwoll) mind pointing out how that PR will fix this problem?

Thanks for the tip! I will definitely look into integrating service worker update checks into my SPA router navigations.

@pavelfeldman
Copy link
Member

However, I don't see how it has anything to do with intercepting and modifying the response for the service-worker.js script request, which, in my case, would allow me to emulate a service-worker.js script change on the server so I can trigger a service worker "update" in the client

Yes, this set of changes does intercept the main request as well!

@pavelfeldman
Copy link
Member

Folding into #1090 as per above.

@rwoll rwoll self-assigned this Jun 13, 2022
@rwoll
Copy link
Member

rwoll commented Jun 13, 2022

Re-opening as this may require additional upstream patching (https://crbug.com/1334900, https://crbug.com/1335463).

For now the only workaround I know of is running a server out-of-band and intercepting/modifying the SW main script requests there. (You can still route the other requests via context/page.)

Since SW main scripts must be same-origin, the page under test would also have to be served/proxied.

@Meir017
Copy link
Contributor

Meir017 commented Jul 10, 2022

a possible workaround right now could be to use the CDP directly with the ServiceWorker domain

const cdp = await context.newCDPSession(page);
await cdp.send('ServiceWorker.enable');
await cdp.send('ServiceWorker.unregister', { scopeURL: 'https://localhost:3000/' });

and then reload the page

@rwoll
Copy link
Member

rwoll commented Jul 11, 2022

Thanks! I've added tests to cover the update case: #15537. For now, the workaround or using an external server to send the new bites is the recommended approach.

@rwoll
Copy link
Member

rwoll commented Jul 11, 2022

tl;dr: Today, you'll get the new Service Worker from the update, but Playwright is currently unable to intercept the update request itself.

@pavelfeldman
Copy link
Member

Why was this issue closed?

Thank you for your involvement. This issue was closed due to limited engagement (upvotes/activity), lack of recent activity, and insufficient actionability. To maintain a manageable database, we prioritize issues based on these factors.

If you disagree with this closure, please open a new issue and reference this one. More support or clarity on its necessity may prompt a review. Your understanding and cooperation are appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants