mockttp
mockttp copied to clipboard
CORS Errors when Using cypress
I'm trying to use MockTTP together with Cypress tests:
I setup HTTP mock as described in the Browser Setup Instructions https://github.com/httptoolkit/mockttp/blob/main/docs/setup.md#browser-setup
I added a "startServer.js" script, which I start before running the tests:
const adminServer = require("mockttp").getAdminServer();
adminServer.start().then(() => {
console.log("Mock server started");
});
My Cypress tests look like this:
const superagent = require("superagent");
const mockServer = require("mockttp").getLocal();
describe("Mockttp", () => {
// Start your mock server
beforeEach(async () => {
await mockServer.start(3030);
});
afterEach(async () => {
mockServer.stop();
});
it("lets you mock requests, and assert on the results", async () => {
// Mock your endpoints
await mockServer.forGet("/mocked-path").thenReply(200, "A mocked response");
// Make a request
const response = await superagent.get("http://localhost:3030/mocked-path");
// Assert on the results
expect(response.text).to.equal("A mocked response");
});
});
I created a fresh ne project based on create-react-app.
Whe running the tests, I get a CORS Error caused by the mockServer.start(3030)
script:
Access to fetch at 'http://localhost:45454/start?port=3030' from origin 'http://localhost:59373' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'localhost:45454' that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I'm using following dependencies:
"cypress": "^10.3.1",
"mockttp": "^3.2.2",
"superagent": "^8.0.0",
CORS Settings.
I have already tried to set the CORS settings to configurations in various ways, but that did not work either:
// server script
...
const adminServer = require("mockttp").getAdminServer({
corsOptions: {
strict: false,
origin: "localhost:45454", // as a alternative i tried the default '*'
preflightContinue: true,
allowPrivateNetworkAccess: true,
},
});
...
// client script
...
const mockServer = require("mockttp").getLocal({ cors: true, debug: true });
...
Any Ideas what I could do? I already checked #39
Hi @derweili. In theory you shouldn't even need that corsOptions
field - that purely restricts CORS to block requests from bad origins. Everything should work out of the box with no special options. I'd suggest removing that entirely for starters, just to check that that's not causing you problems.
Is it possible to share a repo that reproduces this issue, so I can explore it myself? If not, it would be very helpful if you could share a screenshot of the CORS request itself that fails. There must be a response shown in the browser network console somewhere causing this error that includes incorrect CORS headers. You might find https://httptoolkit.tech/will-it-cors/ useful to check what the headers should be for the request.
@pimterry hi, thank your for your help.
Here is my project repo: https://github.com/derweili/cypress-httpmock-test
You can run npm run start:mock-server
to start the mock server and then start cypress with npm run cypress:open
Here are screenshots the failed requests:
What's the error in the console log? Find that's the only useful thing with cors errors
@hanvyj The errors in console are:
Access to fetch at 'http://localhost:45454/start?port=3030' from origin 'http://localhost:65232' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'localhost:45454' that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
POST http://localhost:45454/start?port=3030 net::ERR_FAILED
eval | @ | admin-client.js:79
-- | -- | --
| eval | @ | admin-client.js:8
| ./node_modules/mockttp/dist/client/admin-client.js.__awaiter | @ | admin-client.js:4
| requestFromAdminServer | @ | admin-client.js:75
| eval | @ | admin-client.js:332
| fulfilled | @ | admin-client.js:5
| Promise.then (async) | |
| step | @ | admin-client.js:7
| eval | @ | admin-client.js:8
| ./node_modules/mockttp/dist/client/admin-client.js.__awaiter | @ | admin-client.js:4
| start | @ | admin-client.js:314
| eval | @ | mockttp-client.js:77
| eval | @ | mockttp-client.js:8
| ./node_modules/mockttp/dist/client/mockttp-client.js.__awaiter | @ | mockttp-client.js:4
| start | @ | mockttp-client.js:76
| eval | @ | loadApiData.cy.ts:8
| step | @ | module.js:22
| eval | @ | module.js:22
| eval | @ | module.js:22
| ./cypress/e2e/loadApiData.cy.ts.__awaiter | @ | module.js:22
| eval | @ | loadApiData.cy.ts:7

Try set your origin to:
http://localhost:45454
Instead of just
localhost:45454
I just had to add cors to our mockttp to support credentials: true
because it didn't like the origin as *
.
The main issue from the requests shown here is that the origin is for the wrong server. Access-Control-Allow-Origin
needs to match the origin of the page sending the request, not the server it's sending it to, e.g. http://localhost:65232
.
Normally you'd do this by echoing the value in the origin
header of the request. *
should also work though, except for some special conditions (like authentication headers) but those don't seem to be relevant here...
Are you sure you're seeing the errors above in the example you shared though? I've just cloned that repo, run the commands above and the lets you mock requests, and assert on the results
test passes immediately, with no CORS errors. In that case, it does seem to be sending access-control-allow-origin: *
- this is the full working preflight OPTIONS request & response:
You will see the errors like you've shown above if you set an any incorrect origin
value though, including localhost:45456
, since that won't parse as a valid origin value. We should probably throw an error at startup if you try to set an invalid value like this, to make this easier to spot.
I just had to add cors to our mockttp to support credentials: true because it didn't like the origin as *.
Yes, so credentials are the one case where *
isn't allow in CORS, so you'll need to reflect the origin explicitly. It is a bit awkward to need to manually set the origin to handle that though, hmm...
Internally Mockttp just uses the cors package to implement most of this logic - the corsOptions
option is just passed to that library (here). It looks like that defaults to a origin: '*'
option, which means it will always send that as a fixed value.
@hanvyj If you change that to origin: true
their docs say it should always reflect the incoming origin automatically, which should work out of the box in all cases, without needing to hardcode any addresses. Might not matter, but just FYI.
If the problem is related to auth headers disallowing *
for CORS then this might work for you too @derweili.
I think changing this default here would be a valid non-breaking change that might make CORS much easier for everybody, so do let me know if that works well for either of you, it'd be an easy change.
@pimterry I just found the error.
I was running browserstack local in the background and seems like it uses the same port 45454
.
https://www.browserstack.com/docs/live/local-testing
After I quit browserstack local, the tests run successfull as you described in your comment.
Maybe it would help if starting the getAdminServer
would throw an error, if the port is already in use
I was running browserstack local in the background and seems like it uses the same port 45454.
Ah, that'll do it! Interesting. Glad you managed to chase that down.
Maybe it would help if starting the getAdminServer would throw an error, if the port is already in use
Hmm, I'm not sure how we could handle this better... The admin server does actually throw an error here already, during start()
, although because this is network IO it's asynchronous. The error is thrown, and that then rejects the returned promise. If you don't handle that, in Node <15 it will print an error telling you off, and in Node 15+ it will crash the process entirely.
For example, save the below to a file, and then run it with your node version of choice:
const mockttp = require('mockttp');
mockttp.getAdminServer().start().then(async () => {
console.log('First server started');
await mockttp.getAdminServer().start();
console.log('Second server started (should never happen - port already used)');
});
If you run this with Node 14, it'll print an UnhandledPromiseRejectionWarning
about the EADDRINUSE
error, because there's a promise rejection but no catch()
. If you run this with Node 15+ it will print the same error and then exit immediately with code 1.
Is that different to the behaviour you're seeing?
When trying to start the admin server twice, I get the error you mentioned:
Error: listen EADDRINUSE: address already in use :::45454
But for some reason I did not get this error when the "browserstack local" app was running which also used the same port. The server started "sucessfully"
Can you share the output from netstat -ntl
when running the browserstack app vs when running the Mockttp admin server?
Each one should show a LISTEN line for port 45456, hopefully with some useful differences. I'm wondering if this is an issue with the two services binding on different interfaces, or an IPv4 vs IPv6 issue, or similar. That could cause this, and then requests from elsewhere would unpredictably end up at one or the other two servers, depending on how the client handles that, so Chrome could fail as you're seeing here.
We can probably tweak the server setup to make it more explicitly fail in that case, if so.