cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
46.4k stars 3.14k forks source link

Use service workers in component testing #16742

Open lmiller1990 opened 3 years ago

lmiller1990 commented 3 years ago

Current behavior

Weird behavior with service workers (or at least, mock service worker) in component testing.

See: https://github.com/mswjs/msw/issues/744

Desired behavior

It should work.

Test code to reproduce

https://github.com/mswjs/msw/issues/744#issue-898881738

Versions

csvan commented 2 years ago

Related: https://github.com/cypress-io/cypress/issues/16192

csvan commented 2 years ago

We managed to get Cypress to work with Service Workers using Workbox. Key is simply to tell the SW not to cache (or precache) any Cypress resources:

const navigationRoute = new NavigationRoute(handler, {
  denylist: [
    new RegExp('/__'),
    new RegExp('cypress'),
  ],
});

Then add a rule which forces all Cypress requests to be network-only:

  registerRoute(
    (request) => request.url.includes('cypress'),
    new NetworkOnly(),
  );
yannbf commented 2 years ago

We managed to get Cypress to work with Service Workers using Workbox. Key is simply to tell the SW not to cache (or precache) any Cypress resources:

const navigationRoute = new NavigationRoute(handler, {
  denylist: [
    new RegExp('/__'),
    new RegExp('cypress'),
  ],
});

Then add a rule which forces all Cypress requests to be network-only:

  registerRoute(
    (request) => request.url.includes('cypress'),
    new NetworkOnly(),
  );

Thanks for sharing that @csvan ! @kettanaito do you think this could help msw?

kettanaito commented 2 years ago

Hey, @yannbf. I think it may but I'd like to learn more about the context: what is NavigationRoute? What is NetworkOnly? Is there a solution using a barebone Service Worker API (afaik Workbox is an abstraction on top Service Workers)?

The root issue also seems different at the first glance. MSW doesn't cache anything. So the situation when a Cypress resource would get cached isn't possible. Therefore, configuring any sort of whitelisting on our side is redundant. I think the actual cause for the issue lies elsewhere, and that's where I'd love to hear more feedback from developers investigating what goes wrong when they use Service Workers with Cypress.

fridaystreet commented 2 years ago

thanks @csvan after hours of searching finally found this and while it didn't immediately work and took a bit of figuring out for our setup, managed to get it working in the end. couple points to note for anyone else, hopefully it'll save them a bit more time digging.

I'd agree with you though, this does feel a bit hacky, not sure how long before it breaks. Also have no clue at this stage what the impact of it is on the caching or offline testing. Although we have a PWA our app isn't really intended to be fully offline with local data sync and stuff, so fingers crossed it's ok for now. I did read somewhere, that the SW caching has no affect on cypress. This is certainly true in the testing I saw today, the only real issue is ensuring it forces the cypress url to be network only. But that might only be our PWA config.

  1. Not sure if it's a version thing in cypress, but when we run the tests the term 'cypress' isn't in the url anywhere. the url is /__/#/tests/

I'm not sure if that's a configurable thing, but to get it working and it seemed maybe to ensure we do only capture this for the testing url (who knows someone could have a slug with cypress) we changed it to. Also this is the import for networkOnly, save having to track that down.

import {NetworkOnly} from 'workbox-strategies';

registerRoute(
    (request) => request.url.includes('/__/#/'),
    new NetworkOnly(),
  );
  1. In case it isn't obvious (maybe it was just me), the handler in the navroute is your default handler function. At first I was trying to create a second Navroute for cypress. but the following worked for us with the default workbox handler creation
const handler = createHandlerBoundToURL('/index.html');
const navigationRoute = new NavigationRoute(handler, {
    denylist: [
      new RegExp('/__')
    ],
});
registerRoute(navigationRoute);

Thanks again, really saved us.

lmiller1990 commented 2 years ago

Seems like the best solution here would just be a detailed blog post + example project? Do we need any work in Cypress to facilitate this?

fridaystreet commented 2 years ago

@lmiller1990 yeah sounds good if that's the way you guys like to role. I think there's a number of issues related to service workers spread out over diferent issues, possibly something that could sum them all up in one place with solutions for the ones that have them. I know I just kept on stumbling on all of the others but couldn't find this one until it crossed my mind this could be a issue specific to workbox not just a service worker thing.

vospascal commented 2 years ago

Any update on this ?

lmiller1990 commented 2 years ago

Did you try the work-around suggested above?

Are you trying to use msw or just service workers in general?

lydemann commented 1 year ago

const navigationRoute = new NavigationRoute(handler, { denylist: [ new RegExp('/__'), new RegExp('cypress'), ], });

Do you have a repo with this?

fridaystreet commented 1 year ago

@lydemann not one I can share, but I'll get our test guy to chime in if he can @ashramsey

ashramsey commented 1 year ago

@lydemann not one I can share, but I'll get our test guy to chime in if he can @ashramsey

I'm afraid not, I haven't ever used cypress this way Im sorry.

lydemann commented 1 year ago

We managed to get Cypress to work with Service Workers using Workbox. Key is simply to tell the SW not to cache (or precache) any Cypress resources:

const navigationRoute = new NavigationRoute(handler, {
  denylist: [
    new RegExp('/__'),
    new RegExp('cypress'),
  ],
});

Then add a rule which forces all Cypress requests to be network-only:

  registerRoute(
    (request) => request.url.includes('cypress'),
    new NetworkOnly(),
  );

Thanks for sharing that @csvan ! @kettanaito do you think this could help msw?

Where do you put this in the app, can you share a snippet with imports and everything?

lmiller1990 commented 1 year ago

This works fine in my Vite example app: https://github.com/cypress-io/cypress-component-testing-apps/commit/d71e897e1c246f057a4148ad993acfd54660e020

In before, ensure you return - it's a promise, it needs to resolve and msw has to start before the test runs:

  before(() => {
    // return here! Don't forget.
    return worker.start();
  })
lydemann commented 1 year ago
worker.start

Hi, thanks.

Do you have an Angular example as well? I see you haven't set up MSW for the Angular app here.

lmiller1990 commented 1 year ago

26131 I am having specific issues with Angular. I am trying to debug it, but it's not clear why it won't work, I think Angular's internal webpack config changes something (maybe publicPath) and msw cannot fetch the mockServiceWorker.js file. I don't know why yet.

cypress-app-bot commented 9 months ago

This issue has not had any activity in 180 days. Cypress evolves quickly and the reported behavior should be tested on the latest version of Cypress to verify the behavior is still occurring. It will be closed in 14 days if no updates are provided.

lydemann commented 9 months ago

Has anyone got a solution or a workaround for this in an Angular app?

OliDM commented 2 weeks ago

setup

What didn't work

using the regular mockServiceWorker.js on ./public component tests would refresh on an infinite loop when trying to start the worker:

in all these cases the worker would be started as worker.start({ serviceWorker: { url: "http://localhost:5173/__cypress/src/mockServiceWorker.js" } });

what sort of worked

I modified the cypress 13.11.0 installation files to add the service worker script at the top most frame of the runner, the worker would register correctly no looping but was not able to capture request happening inside the spec runner. My theory is that this did not work because of some reasons:

What worked

Placing a copy of mockServiceWorker.js inside AppData\Local\Cypress\Cache\13.11.0\Cypress\resources\app\packages\app\dist\assets\ allowed me to change the worker start url to worker.start({ serviceWorker: { url: "http://localhost:5173/__cypress/iframes/mockServiceWorker.js" } });, this would enable the service worker to register correctly, have correct scope and be able to capture request.

From here I was able to initialize the worker directly inside support/component.ts, using the event test:before:run:async or on a before call inside the specs, warning there are timing issues, on some of these approaches the specs run before the worker is finished setting up.

Sorry I don't have a solution to set all this up, just wanted to share what I've found so far, hopefully this can hint to a proper solution.