Cypress e2e testing - How to get around Cross Origin Errors?
Asked Answered
P

5

22

I'm testing a web app that integrates Gmail, Slack, Dropbox etc. I'm trying to write end to end tests with Cypress.io to verify that auth flows are working. Cypress restricts me from navigating outside my app's domain and gives me a Cross Origin Error. The Cypress docs say that testing shouldn't involve navigating outside your app. But the entire purpose of testing my app is to make sure these outside auth flows are functioning.

The docs also say you can add "chromeWebSecurity": false to the cypress.json file to get around this restriction. I have done this, but am still getting cross origin errors (this is at the heart of my question. I would ideally get around this restriction).

I have attempted cypress' single-sign-on example. https://github.com/cypress-io/cypress-example-recipes#logging-in---single-sign-on I was not able to make it work, and it's a lot more code than I think is necessary.

I've commented on this thread in github, but no responses yet.

Full error message:

Error:     CypressError: Cypress detected a cross origin error happened 
on page load:

  > Blocked a frame with origin "https://www.example.com" from 
accessing 
    a cross-origin frame.

    Before the page load, you were bound to the origin policy:
      > https://example.com

A cross origin error happens when your application navigates to a new 
superdomain which does not match the origin policy above.

This typically happens in one of three ways:

1. You clicked an <a> that routed you outside of your application
2. You submitted a form and your server redirected you outside of your 
application
3. You used a javascript redirect to a page outside of your application

Cypress does not allow you to change superdomains within a single test.

You may need to restructure some of your test code to avoid this 
problem. 

Alternatively you can also disable Chrome Web Security which will turn 
off this restriction by setting { chromeWebSecurity: false } in your 
'cypress.json' file.

https://on.cypress.io/cross-origin-violation
Perky answered 23/5, 2018 at 19:21 Comment(4)
I assume you are going to a 3rd party superdomain that is not under your control. If this is true then you should not be navigating to that page in cypress, and there could be a few reasons why it's breakingGothard
In some scenarios people are going to have applications that are protected by Sign-in that is out of their control and off domain. For example we have an authentication flow that can't be circumvented by just POSTing to the Slack Oauth endpoints. In this scenario, whilst not ideal, it's preferable to be able to navigate Cypress to another domain and sign-in. Rather than repeat the docs' purist view of "don't do this" it would be useful to help towards a workaround.Whitefish
@Bill Mayo did you ever find a solution to this? The error message implies your cypress.json isn't being picked up.Whitefish
I have this same problem -- trying to authenticate with Slack. I wonder if making some kind of stub for the 3rd party server is what needs to happen here. Seems like this would require more work, but maybe it's the best practice. Anyways, Cypress has an example for logging in with a 3rd party server, which says: > Login when authentication is done on a 3rd party server. github.com/cypress-io/cypress-example-recipes/tree/master/…Platonism
C
6

setting { "chromeWebSecurity": false } in my 'cypress.json' file worked for me

Cogwheel answered 16/11, 2021 at 10:16 Comment(6)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Sternum
The json key should be in quotes { "chromeWebSecurity": false }Jesusitajet
This answer might also help.Ardelia
@RamV Many keys are not in quotes why? Like pageLoadTimeout: 5000Naivete
@Naivete Valid JSON must have double-quoted strings as keys. You've likely seen plain JS with data structures without the quotes.Trackless
In the latest version of cypress the config is in cypress.config.js rather than cypress.json, so the quotes around chromeWebSecurity can be omitted.Bootless
S
5

Bill Mayo wanted to run the Cypress single-sign-on example app but it was throwing an origin error.

Making the example work has nothing to do with the cy.origin() command, here are the steps to run this example.

Updated link 5 Jan 2024

The link to this example has changed it's now https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/logging-in__single-sign-on

Making it work

  • download the repo (entire examples repo) and install dependencies in the project root yarn or npm i.

  • The package.json has a restriction on Node version in this section

    "engines": {
      "node": "^18.16.0"
    },
    

    but the current LTS version of Node is 20.10.0, so if like me you have that version, delete that section of package.json.

  • switch to the logging-in__single-sign-on sub-folder and edit the scripts in package.json

  • the start script doesn't seem to work, the auth server did not start (maybe a Windows issue)

  • add new scripts to start the app server and the auth server separately:

    "scripts": {
      ...
      "start:app:server": "node app_server.js --port 7074",
      "start:auth:server": "node auth_server.js --port 7075",
    
  • run three terminal sessions, in each run

    • yarn start:app:server
    • yarn start:auth:server
    • yarn cypress:open
  • run the spec that is in this project, it completes and passes

enter image description here


The code for single-sign-on test example

Since the link may change again, here is the abbreviated test code

const _ = Cypress._
const url = require('url')

describe('Logging In - Single Sign on', function () {

  Cypress.Commands.add('loginBySingleSignOn', (overrides = {}) => {
    Cypress.log({name: 'loginBySingleSignOn'})
    const options = {
      method: 'POST',
      url: 'http://auth.corp.com:7075/login',
      qs: {
        redirectTo: 'http://localhost:7074/set_token',
      },
      form: true,
      body: {
        username: 'jane.lane',
        password: 'password123',
      },
    }
    _.extend(options, overrides)
    cy.request(options)
  })

  context('Use redirectTo and a session cookie to login', function () {
    it('is 403 unauthorized without a session cookie', function () {
      cy.visit('/dashboard')
      cy.get('h3').should('contain', 'You are not logged in and cannot access this page')
      cy.url().should('include', 'unauthorized')
    })

    it('can authenticate with cy.request', function () {
      cy.getCookie('cypress-session-cookie').should('not.exist')
      cy.loginBySingleSignOn().then((resp) => {
        expect(resp.status).to.eq(200)
        expect(resp.body).to.include('<h1>Welcome to the Dashboard!</h1>')
      })
      cy.getCookie('cypress-session-cookie').should('exist')
      cy.visit('/dashboard')
      cy.get('h1').should('contain', 'Welcome to the Dashboard')
    })
  })

  context('Manually parse id_token and set on local storage to login', function () {
      it('knows when there is no session token', function () {
        cy.visit('/')
        cy.get('#main').should('contain', 'No session token set!')
      })

      const responseToToken = (resp) => {
        const uri = url.parse(resp.redirectedToUrl, true)
        expect(uri.query).to.have.property('id_token')
        return uri.query.id_token
      }

      it('can parse out id_token and set on local storage', function () {
        cy.loginBySingleSignOn({ followRedirect: false })
        .then(responseToToken)
        .then((id_token) => {
          cy.intercept('/config').as('getConfig')
          cy.visit('/', {
            onBeforeLoad (win) {
              win.localStorage.setItem('id_token', id_token)
            },
          })

          cy.wait('@getConfig')
            .its('response.body')
            .should('deep.eq', {foo: 'bar', some: 'config', loggedIn: true})

          cy.get('#main')
            .invoke('text')
            .should((text) => {
              const json = JSON.parse(text)
              expect(json).to.deep.eq({foo: 'bar', some: 'config', loggedIn: true})
          })
        })
      })

      describe('Log in once for speed', () => {
        before(function () {
          cy.loginBySingleSignOn({ followRedirect: false })
            .then(responseToToken)
            .as('token') // saves under "this.token"
        })

        beforeEach(function () {
          cy.on('window:before:load', (win) => {
            win.localStorage.setItem('id_token', this.token)
          })
        })

        it('opens page as logged in user', () => {
          cy.visit('/')
          cy.contains('"loggedIn":true')
        })

        it('config returns logged in: true', function () {
          cy.intercept('/config', (req) => {
            delete req.headers['if-none-match']
          }).as('getConfig')

          cy.visit('/')

          cy.wait('@getConfig').then((xhr) => {
            expect(xhr.request.headers, 'request includes token header')
              .to.have.property('x-session-token', this.token)

            expect(xhr.response.body, 'response body')
              .to.deep.equal({foo: 'bar', loggedIn: true, some: 'config'})
          })
        })
      })
    }
  )
})
Siglos answered 5/1 at 10:8 Comment(0)
J
3

If you are trying to assert the proper navigation to gmail...

You should stub the function that handles that and assert that the request contains the necessary key value pairs. Without more information on the intent of this test it is hard to give specific advice. It sounds like you would want to have a "spy"(type of test double).

Here is the documentation for spies: https://docs.cypress.io/guides/guides/stubs-spies-and-clocks.html#Stubs

If you are trying to verify the contents of the email

You will want to use a library to handle reading gmail. cy.task can be used to invoke JavaScript from an external library. This Medium article has a good write up on how to do this.

Medium article: https://medium.com/@levz0r/how-to-poll-a-gmail-inbox-in-cypress-io-a4286cfdb888

TL;DR of article

  • Setup and define the custom task(method) that will check gmail(uses "gmail-tester" in the example)
  • Use cypress to trigger the email(obviously)
  • Capture/define data(like email subject, dynamic link, email content)
  • Assert the data returned from gmail-tester is as expected

DON'T

Use the GMail UI in your test in an effort to avoid test flake (all UI testing has flakiness), and potential UI changes to the Gmail app that require updates to your test. The backend methods that gmail-tester uses are less likely to change overtime compared to the UI. You also avoid the CORS error.

Disabling cross-origin security, if you must...(eek bugs!)

If you must, add chromeWebSecurity: false to the cypress.json config file. Be sure to add it inside of the curly braces. There should only be one set of braces in that file.

NOTE: One cannot simply use cy.visit(<diffSuperDomain>); there is an open issue. Apparently this is a very difficult change to make in cypress.

One potential workaround is to only have one super domain per test. It should work if you set the chromeWebSecurity: to false and only have one domain per test(it block). Careful, as it opens you up to cascading failures as one test will rely on the next. Hopefully they fix this soon.

https://docs.cypress.io/guides/guides/web-security.html#Disabling-Web-Security

Josphinejoss answered 2/1, 2020 at 22:53 Comment(0)
F
0

Since Cypress 12 your tests can operate in multiple domains using the origin command. Example from the official blog (but fixed formatting):

it('navigates', () => {
  cy.visit('/')
  cy.get('h1').contains('My Homepage')
  cy.origin('www.acme.com', () => {
    cy.visit('/history/founder')
    cy.get('h1').contains('About our Founder, Marvin Acme') // 👍
  })
})

At that blog entry there are also examples how to use this to authenticate at another domain. Worked fine for me with Keycloak using both Chrome and Firefox.

Note: In fact this was introduced with Cypress 9.6, but only since Cypress 12 you don't need to set "experimentalSessionAndOrigin": true in cypress.json anymore. Also this config option was removed with Cypress 12.

Favela answered 5/5, 2022 at 11:1 Comment(0)
W
-4

There are a few simple workarounds to these common situations: Don’t click <a> links in your tests that navigate outside of your application. Likely this isn’t worth testing anyway. You should ask yourself: What’s the point of clicking and going to another app? Likely all you care about is that the href attribute matches what you expect. So make an assertion about that. You can see more strategies on testing anchor links in our “Tab Handling and Links” example recipe.

You are testing a page that uses Single sign-on (SSO). In this case, your web server is likely redirecting you between superdomains, so you receive this error message. You can likely get around this redirect problem by using cy.request() to manually handle the session yourself.

If you find yourself stuck and can’t work around these issues you can just set this in your cypress.json file. But before doing so you should really understand and read about the reasoning here.

// cypress.json

{
  "chromeWebSecurity": false
}
Webfooted answered 31/7, 2019 at 14:30 Comment(1)
This is just a copy paste from the docs and doesn't actually answer the original question which already mentions adding the chromeWebSecurity flag to the configuration file.Whitefish

© 2022 - 2024 — McMap. All rights reserved.