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

CORS Errors when Using cypress #100

Open
derweili opened this issue Jul 31, 2022 · 10 comments
Open

CORS Errors when Using cypress #100

derweili opened this issue Jul 31, 2022 · 10 comments

Comments

@derweili
Copy link

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

@pimterry
Copy link
Member

pimterry commented Aug 1, 2022

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.

@derweili
Copy link
Author

derweili commented Aug 2, 2022

@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:
Bildschirmfoto 2022-08-02 um 09 35 43
Bildschirmfoto 2022-08-02 um 09 35 47
Bildschirmfoto 2022-08-02 um 09 35 57

@hanvyj
Copy link
Contributor

hanvyj commented Aug 2, 2022

What's the error in the console log? Find that's the only useful thing with cors errors

@derweili
Copy link
Author

derweili commented Aug 2, 2022

@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

Bildschirmfoto 2022-08-02 um 14 15 00

@hanvyj
Copy link
Contributor

hanvyj commented Aug 2, 2022

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 *.

@pimterry
Copy link
Member

pimterry commented Aug 2, 2022

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:

Screenshot from 2022-08-02 22-31-49

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.

@derweili
Copy link
Author

derweili commented Aug 4, 2022

@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

@pimterry
Copy link
Member

pimterry commented Aug 4, 2022

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?

@derweili
Copy link
Author

derweili commented Aug 4, 2022

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"

@pimterry
Copy link
Member

pimterry commented Aug 4, 2022

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.

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

No branches or pull requests

3 participants