Open yannbf opened 3 years ago
The effect is related to the following code: https://github.com/mswjs/msw/blob/86c23ec16c10698eb329798e883980a9b9159f05/src/setupWorker/start/utils/getWorkerInstance.ts#L25-L33
commenting it out removes the loop and flaky behavior, but of course msw doesn't work as expected
Hey, @yannbf. Thanks for reporting this.
The question is why navigator.serviceWorker.controller
becomes undefined?
It's highly recommended to have the MSW set up on the application's side. That way your app registers the worker, and Cypress loads your app that already has the mocking enabled. If you absolutely must have MSW integration on the Cypress side, I'd imagine it being set up in some global hook (i.e. before all test suites) as opposed to a single test's before
.
The question is why
navigator.serviceWorker.controller
becomes undefined?It's highly recommended to have the MSW set up on the application's side. That way your app registers the worker, and Cypress loads your app that already has the mocking enabled. If you absolutely must have MSW integration on the Cypress side, I'd imagine it being set up in some global hook (i.e. before all test suites) as opposed to a single test's
before
.
Great one. Cypress component test runner does not rely on having an application anymore, as it bootstraps components in isolation. So indeed msw should be set somewhere at a higher level once. I tried setting it up in a support file (and updated the repro repo), and the result is that cypress fails (the test is just asserting if the element exists), then if I retry, it gets in an infinite loop.
@lmiller1990 I heard you were looking into msw integration with CT. Do you think it's currently possible?
My first instinct was to do exactly what you are doing, and set it up inside of beforeEach
(or before
). I am also curious as to why navigator.serviceWorker.controller
is undefined. I've used msw many times before (but not with Cypress) and not seen this before.
I don't see any reason why this isn't possible - but I also don't see an obvious reason why this isn't working in the first place. I am not sure I can prioritize this dive right now, but I would like to use msw in Cypress, so I'll try to explore this a bit more at some point in the near future. I'll follow this thread and post anything that comes to mind.
According to MDN:
The controller read-only property of the ServiceWorkerContainer interface returns a ServiceWorker object if its state is activating or activated (the same object returned by ServiceWorkerRegistration.active). This property returns null if the request is a force refresh (Shift + refresh) or if there is no active worker.
I wonder if we are doing a refresh somewhere on the Cypress end that would be causing the last part of this to occur.
Edit: created this issue for more visibility.
Hey @lmiller1990 thanks a lot for helping out with this! I noticed that the service worker is only registered after running the test. I believe that ideally the service worker registration should happen as soon as cypress is bootstrapped.
Is there any way of running a "global setup" code that executes when cypress is open, without having to running any test suite? If we had a way to do that, I believe msw could work! As an example, in Storybook there is a preview.js
file which executes once Storybook is bootstrapped. By setting msw in that file, it works across every story. The service worker is registered outside of the context of a story, and is optionally configured when running stories.
Normally we recommend using before
or beforeEach
for this - seems like a race condition.
edit: I see what you are doing now: https://github.com/yannbf/msw-cypress-component-issue/blob/74115f93f9cd7d5239b01d56501d6fbd248294dc/cypress/support/index.js. You can actually put a before
or beforeEach
hook in support/index.js
. As long as it returns a Promise, Cypress will wait. So something like:
before(() => {
return new Promise(res => {
/* need some way to know when MSW has started, then resolve the promise */
setupWorker(...).start()
})
})
We need some way to know when MSW has started. Is there something like that? if start()
returns a Promise, that would be perfect.
For future reference, Cypress does expose some event. You don't see them often, since you generally should just use before
or beforeEach
. The events are documented here. test:before:run
and test:before:run
might be useful. They work like Cypress.on('test:before:run', (attributes , test) => { /* stuff */ })
.
@lmiller1990 calling worker.start()
does return a Promise that indicates when the worker is activated and ready to process requests. You can await that promise in your before
hook.
The navigator.serviceWorker.controller
may be undefined because the worker is not registered in the origin where the code tries to resolve its controller. Cypress runs in its own context (and host), while the tested app has its own context (and that's where the worker is registered). We have a custom logic on the worker's side to look up the proper registration and respect it when working with cypress (support for registering the worker in a nested iframe to still affect the requests of the parent frame). It just may happen that accessing the controller in the parent frame doesn't resolve to the child frame's controller. This needs to be investigated. We have iframe integration tests where you can inspect how the controller
behaves in the case of nested iframes:
I don't have the capacity to look into this at the moment but will keep an eye on this issue to help in whichever way I can. Thank you all for the discussion on this topic.
I not sure i run into exact some problems
I try integrate MSW with cypress components test
import { useAsyncFn } from "react-use"
export function TestApp({ children }) {
const [
{
value: startedMockServer,
loading: startMockServerLoading,
error: startMockServerError,
},
startWorkerFunction,
] = useAsyncFn(async () => {
await mockWorker.start({
serviceWorker: {
url: 'https://cdn.jsdelivr.net/npm/msw/lib/esm/mockServiceWorker.js',
},
});
window.msw = {
worker: mockWorker,
};
console.log('MockServiceWorker Init');
return true;
}, []);
useEffect(() => {
if (!startedMockServer && !startMockServerLoading) {
startWorkerFunction();
}
}, [startedMockServer, startWorkerFunction, startMockServerLoading]);
if (startMockServerLoading) return null;
if (startMockServerError)
throw new Error('Init TestApp error');
return (
<>{children}</>
);
}
I attempt create TestApp for configure test environment first.
but it suck in mockWorker.start()
, the promise just never return
I have attempt add some log to debugging
Turn out i find i suck here, the worker just never response the event
I can confirm that the issue exists but have not enough insights to share as to its cause. Feel free to share your findings, it's a general network/javascript debugging that should take place to find out what goes wrong. Thank you for your patience.
I've had some luck with the following approach:
cypress/support/msw.js
:
import { setupWorker } from 'msw'
export const serviceWorker = setupWorker()
export const waitForServiceWorker = serviceWorker.start({
serviceWorker: {
url: '/mockServiceWorker.js',
},
})
cypress/support/index.js
:
import './msw'
src/components/example.spec.js
:
import { serviceWorker, waitForServiceWorker } from '../../cypress/support/msw'
import { rest } from 'msw'
describe('example', function () {
before(() => waitForServiceWorker)
afterEach(() => serviceWorker.resetHandlers())
it('makes a request', function () {
serviceWorker.use(
rest.post('/foo', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ foo: 'bar' })
)
})
)
mount(<ComponentThatMakesARequest/>)
})
})
I'm now hitting https://github.com/cypress-io/cypress/issues/14745 but that seems to be a separate issue - I can at least see the response being served by the msw ServiceWorker.
I'm trying to get this working as well but I can't even get past the fact that the mockServiceWorker.js file cannot be served during component testing. My app is running on localhost:3000 and the component tests run on localhost:8080. If I try to point it to localhost:3000/ i get the following:
serviceWorker: {
url: 'http://localhost:3000/mockServiceWorker.js',
},
[MSW] Failed to register the Service Worker:
Failed to register a ServiceWorker: The origin of the provided scriptURL ('http://localhost:3000') does not match the current origin ('http://localhost:8080').
If I try to path it relatively (serviceWorker: {url: 'dist/public/mockServiceWorker.js'},
) I get the following that The script has an unsupported MIME type ('text/html').
If I try to go from a base path I get the following:
serviceWorker: {url: '/dist/public/mockServiceWorker.js'},
[MSW] Failed to register a Service Worker for scope ('http://localhost:8080/') with script ('http://localhost:8080/dist/public/mockServiceWorker.js'): Service Worker script does not exist at the given path.
Did you forget to run "npx msw init <PUBLIC_DIR>"?
How can I get past this? I know that I need to have the service worker file alongside the server running on 8080 but I don't know where I can even copy that file to since Cypress is just kind of sandboxing this whole thing.
Any help would be appreciated.
Hey, @reintroducing. The worker script must always be served from the same hostname. You cannot have a worker at :3000
and handle requests that happen at :8000
βthat's against the security design of service workers and any browser will forbid this.
Your second approach is correct. Only you're pointing at a non-existing resource: there's nothing at dist/public/mockServiceWorker.js
. You get the Mime type error because accessing that URL likely produces a 404 page (HTML page). You can try following that URL yourself and see where it leads.
My suspicion is that you need to omit the /dist
part of your worker script URL. I assume that /dist
is the public folder, which is the root of your application. I think even /public
looks suspicious in the URL but you're the best to verify this.
http://localhost:8080/mockServiceWorker.js
but I may be wrong. serviceWorker.url
option to worker.start()
.@kettanaito Thanks for your reply. My apologies, I left out the fact that I had tried all the possibly URLs I could think of at :8080
as well but I basically just get the ole Cannot GET /mockServiceWorker.js
with the path changing based on what I was trying. The reason for this is because I don't know where in the filesystem Cypress is serving the files from and therefore I don't know where I should copy the mSW.js file into for it to be served accordingly alongside the component testing application. Here is an example URL of my App.spec (http://localhost:8080/__/#/tests/component/app/App.spec.js) but again, I can't figure out where its being served from as it pertains to the filesystem itself.
I was able to resolve my issue when I realized that webpack for the cypress component tests was being served on localhost:8080/public (whereas my app serves on localhost:3000/dist/public, long story, dont ask). Once i realized that I was able to copy the worker file into the /public directory and didn't need the file path in the worker.start
call since its served from /
by default.
Any updates on this?
I'm having the same problem, and none of the above workarounds have worked for me. I'd really love to be able to use MSW in my Cypress tests, is there any progress on this issue?
Any updates on this? I'm having the same situation and even Cypress is updated a lot with new version. It's really hard to know cypress new versions problem or msw problem. Below is my procject's version. Please share some informations who has same situation with this issue.
"cypress": "^10.11.0",
"msw": "^0.47.4",
There is a minimal reproduction in the OP, we should update it to the latest version of everything and see if it's still happening. I suspect it's a config issue.
Also we have an issue in Cypress to track and work needed on our end: https://github.com/cypress-io/cypress/issues/16742
I'm running in the same problem as well. And tried a lot of different (suggested) solutions. But none seem to work.
As soon as MSW is loaded, Cypress gets in some sort of reload loop.
And I'm getting the following errors:
[MSW] Cannot intercept requests on this page because it's outside of the worker's scope ("http://localhost:8080/__cypress/src/"). If you wish to mock API requests on this page, you must resolve this scope issue.
- (Recommended) Register the worker at the root level ("/") of your application.
- Set the "Service-Worker-Allowed" response header to allow out-of-scope workers.
Which is correct. And setting the scope to '/'
does not help.
Sometimes it correctly says: MSW Enabled
, but the loop still happens.
I'm trying to get MSW working in combination with Cypress Component tests in a Nx workspace (so I can write a blog about it). Running the application, with 'normal' Cypress tests and Jest tests with MSWjs all work perfectly ππΌ
Example repo: https://github.com/the-ult/angular-nx-playground/. (Work in progress π )
And the tests are in:
Cypress Component Test setup for movie/feature-movies
:
The mockServiceWorker.js
is located in: https://github.com/the-ult/angular-nx-playground/tree/main/libs/shared/test/msw/src/assets
package.json
"msw": {
"workerDirectory": "libs/shared/test/msw/src/assets"
},
And (automatically) served on: https://localhost:8080/__cypress/src/mockServiceWorker.js
Where should mockServiceWorker.js
be placed so it is served on http://localhost:8080/mockServiceWorker.js
?? Or is it correct it is served now? But how do we get the correct scope?
What would be the proper way to setup MSW with Nx (separate libs)?
What I would like to try to setup is a more global setup method in libs/shared/test/msw/browser
which can be loaded in the specific (integration) Component
Tests in the before()
;
Node : 18.12.1
OS : darwin x64
pnpm : 7.18.2
nx : 15.3.3
@nrwl/angular : 15.3.3
@nrwl/cypress : 15.3.3
@nrwl/devkit : 15.3.3
@nrwl/eslint-plugin-nx : 15.3.3
@nrwl/jest : 15.3.3
@nrwl/js : 15.3.3
@nrwl/linter : 15.3.3
@nrwl/nx-cloud : 15.0.2
@nrwl/webpack : 15.3.3
@nrwl/workspace : 15.3.3
typescript : 4.8.4
cypress: 12.1.0
msw: 0.49.2 // Tried 0.0.0-fetch.rc-3 as well
@the-ult that is really interesting, I was working on something similar to this earlier: https://github.com/cypress-io/cypress/pull/25120
You say it's only happening in CT and not E2E - I guess what might be happen is that, instead of the request going to the main network layer, it heads to the dev server route instead, and this messes things up.
I'll try your repo. Do I just clone and run something? yarn cypress open
maybe?
Also stupid question, but what does MSW do that cy.intercept()
doesn't (this should be vastly more powerful, understandable if you just want to use MSW since you are migrating from Jest or something and don't want to touch your code too much).
Also stupid question, but what does MSW do that
cy.intercept()
doesn't (this should be vastly more powerful, understandable if you just want to use MSW since you are migrating from Jest or something and don't want to touch your code too much).
@lmiller1990 im not the poster but I can tell you why we use msw over cy.intercept in our app. We use msw extensively to mock just about every api during development so we donβt have to depend on a BE server to run alongside our FE app. Then, during tests, msw is utilized to mock those same calls so we never have to write a cy.intercept in our tests and the api calls βjust workβ. It serves double duty!
@reintroducing thanks, I figured it was something like this - makes sense.
what does MSW do that cy.intercept() doesn't
@lmiller1990, cy.intercept()
is bound to Cypress. MSW is framework-, tool-, and environment-agnostic. It's simply a better time investment that allows to reuse API mocks across the entire stack.
MSW also works completely differently compared to cy.intercept()
, since MSW doesn't patch request clients or meddle with internal browser flags. It uses standard web APIs to allow requests to fully execute, intercepting them at the network level.
Also stupid question, but what does MSW do that
cy.intercept()
doesn't (this should be vastly more powerful, understandable if you just want to use MSW since you are migrating from Jest or something and don't want to touch your code too much).
There are no stupid questions π
In my previous project we were working with GraphQL. And intercepting GraphQL requests in earlier version of Cypress (pre-intercept) was a bit cumbersome. With MSWjs this was easy.
But the greatest 'win' of MSWjs is one-easy-way for API mocking for everything.
So for everything you can use the same API and easily re-use mock-data and Handlers. Without having to learn cy.intercept
, jest Spy/Mock, etc.
(That's why I was trying to create this sample repo π )
I'll try your repo. Do I just clone and run something?
yarn cypress open
maybe?
Good question. I'll update the README.md and add install/run instructions.
I'm using pnpm
pnpm i
pnpm start:msw movie-db
pnpm test
// OR
nx test --runInBand
pnpm e2e:msw
// OR
nx e2e movie-db-e2e --watch --browser=chrome
pnpm exec nx run movie-feature-media-items:component-test --skip-nx-cache=true --watch=true --browser=chrome
// OR
nx run movies-feature-movies:component-test --watch=true --browser=chrome
media-card
works since it does not load mswmovies-page
loads and gets in the loopOk thanks @kettanaito and @the-ult. I'll poke around Cypress a bit, it probably won't be until next week though. I'm fairly sure something is getting muddled up in the routing for CT.
The CT specific routes are here https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/routes-ct.ts The shared routes are https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/routes.ts
Small files, so this should not be too hard to debug.
Awesome. In de meantime, I'll try and fix the E2E and improve the documentation ππΌ
And see if I can debug some myself as well
[edit] E2E is working again. Monday I'll improve the documentation
Thanks for all the involvement on this, folks! Let me know if I can be of any help. I should be available sometime at the beginning of the next year.
Any news on this? This is a game changer once working.
I updated my sample project a little bit. But did not get the component tests working. Haven't had time to try and debug the files @lmiller1990 mentioned.
Will try to give it another look next week
Also seems the service worker is not proxied to the right Cypress asset location when requested:
Hey, folks. I'm sorry if this is blocking your development. As the only person maintaining this project, there's only that much I can do. Right now, I'm focusing on features and bug fixes that more people can benefit from (and even that takes a lot of time as I develop MSW in my free time). This is an open-source project so anybody can fork it, dive into the issue, debug it, and share their findings. I can then provide my expertise and help you merge a meaningful bugfix to solve it for everyone.
I'm trying to help where I can but I am a human and some things do get overlooked. There are also things that I have zero interest in and that I will likely never address. That's the balance that helps me keep this whole open source thing afloat.
@lydemann, that looks like you're not serving the worker script with Cypress for one reason or another. Double-check where you're initiating MSW and whether the worker is available at that path (most likely it's not, thus 404).
Hey @kettanaito,
Definitely understand. And really appreciate all the work you are doing for this amazing project! ππΌ
Haven't had time to dive any further in this issue. Hopefully I'll be able to make some time and effort next week (again π ).
Hey, folks. I'm sorry if this is blocking your development. As the only person maintaining this project, there's only that much I can do. Right now, I'm focusing on features and bug fixes that more people can benefit from (and even that takes a lot of time as I develop MSW in my free time). This is an open-source project so anybody can fork it, dive into the issue, debug it, and share their findings. I can then provide my expertise and help you merge a meaningful bugfix to solve it for everyone.
I'm trying to help where I can but I am a human and some things do get overlooked. There are also things that I have zero interest in and that I will likely never address. That's the balance that helps me keep this whole open source thing afloat.
@lydemann, that looks like you're not serving the worker script with Cypress for one reason or another. Double-check where you're initiating MSW and whether the worker is available at that path (most likely it's not, thus 404).
It seems that it is not proxied correctly to /__cypress/src/mockServiceWorker.js by Cypress. I can still fetch the correct file with:
worker.start({
serviceWorker: {
url: '/__cypress/src/mockServiceWorker.js',
},
});
I made a video going through the specific problem:
https://www.loom.com/share/e200a7c9311d4b90af3899d04623ac0f
If anybody has any ideas on how to proceed from here it is much appreciated.
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();
})
It doesn't seem to work in this user's Angular app, though: https://github.com/cypress-io/cypress/issues/26131
~I wonder if the problem lies in the cypress/webpack-dev-server
somehow messing up the request.~ It seems to work fine with a simple webpack example I created, too.
@the-ult I am looking at your repro: https://github.com/mswjs/msw/issues/744#issuecomment-1351491630
How do I launch the component tests? I tried pnpx nx component-test movie-db
but no luck.
I added 1 component-test atm:
nx run movie-feature-media-items:component-test --watch
Updated the project a little. But the component-test is broken atm. (I'll try to make some time this week to fix the tests. )
Somehow the scope is incorrect. (What should it be?)
[MSW] Failed to register the Service Worker:
Failed to register a ServiceWorker for scope ('http://localhost:8080/') with script
('http://localhost:8080/__cypress/src/mockServiceWorker.js'):
The path of the provided scope ('/') is not under the max scope allowed
('/__cypress/src/'). Adjust the scope, move the Service Worker script,
or use the Service-Worker-Allowed HTTP header to allow the scope.
(And have to figure out how we can run/show all component-tests.. There's probably a way for it.. Just haven't looked at it yet)
This is the exact problem I'm running into:
Failed to register a ServiceWorker for scope ('http://localhost:8080/') with script
('http://localhost:8080/__cypress/src/mockServiceWorker.js'):
It is only happening for Angular projects - all the others I've tried work fine. I think Angular's (incredibly complex) webpack config is routing the request for the mockServiceWorker.js
somewhere incorrect. I know there's an API in msw to change where the mockServiceWorker.js
is loaded from, I tried many different things using that, but still no dice.
Yes indeed. I had it working (somehow) before. But then I got the loop.. Maybe @kettanaito could be of help?
Gonna try and give it another go tonight.
I do not think this is something @kettanaito can do, it seems like we need to do something in our own codebase. One other thing I'd try is verifying msw works in the browser outside of Cypress with Angular - to prove it's a Cypress x Angular webpack specific issue, which I suspect it is.
I do not think this is something @kettanaito can do, it seems like we need to do something in our own codebase. One other thing I'd try is verifying msw works in the browser outside of Cypress with Angular - to prove it's a Cypress x Angular webpack specific issue, which I suspect it is.
MSW works just fine in a browser outside of Cypress :) It's within the component test things go in a loop.
Any news? @lmiller1990
I spent a good chunk of time debugging this in https://github.com/cypress-io/cypress/issues/26131 but I didn't figure out the issue. It seems specific to webpack and Angular. π’
Damn that must be really tricky. No workarounds?
I am also having this same issue but with a NextJs project instead of Angular. If anyone discovers anything here let me know!
Any more luck with this? @lmiller1990
Hey @lydemann - I'm sorry, I haven't looked at this issue since.
Ref: NextJS issues
I found when using basePath
in nextConfig
(e.g. /foo
), you need to set serviceWorker.options.scope to /foo
otherwise the scope will be set to /foo/
which wont match your root page.
worker.start({
serviceWorker: {
options: { scope: '/foo' },
url: '/foo/mockServiceWorker.js',
},
});
See: validateWorkerScope.ts. '/foo'.startsWith('/foo/')
will always be false.
Ref: NextJS issues
I found when using
basePath
innextConfig
(e.g./foo
), you need to set serviceWorker.options.scope to/foo
otherwise the scope will be set to/foo/
which wont match your root page.worker.start({ serviceWorker: { options: { scope: '/foo' }, url: '/foo/mockServiceWorker.js', }, });
See: validateWorkerScope.ts.
'/foo'.startsWith('/foo/')
will always be false.
Does this work for Angular as well?
Describe the bug
Cypress 7 recently released an interesting feature called component test runner. It allows to mount a component and run it in isolation, rather than having to run the entire application. I was trying to use it with MSW (which would be awesome!), but for some reason, starting the service worker makes the test behave weirdly, becoming flaky, sometimes looping and causing trouble.
Environment
msw: 0.28.2
nodejs: 14.14.0
npm: 6.4.8
To Reproduce
Steps to reproduce the behavior:
before, check this repro: https://github.com/yannbf/msw-cypress-component-issue
start
of the service worker, the tests flow naturally. When you add them, there's something weird that results in cypress runner looping.Expected behavior
MSW should not impact cypress
Screenshots