How to login in Auth0 in an E2E test with Cypress?

Mikel picture Mikel · Jul 6, 2018 · Viewed 8.7k times · Source

I have started testing a react webapp but I didn't go far because I had issues with the login. I am using cypress e2e testing tool.

A welcome page is shown with a button to login, which will redirect you to auth0 service. User is login with email and password , then is redirected back to the webapp with a token.

I tried many different approach each of them resulting in a different problem.

Note: I don't want to test Auth0, I just want to enter in my webapp.

Attempt 1. Clicking on login button

Tried: Cypress should do the same as what the user does, therefore the test will click login button and go to Auth0 and fill in credentials. Problem: Cypress doesn't allow you to navigate to another domain during the test.

Because Cypress changes its own host URL to match that of your applications, it requires that your application remain on the same superdomain for the entirety of a single test.

You are supposed to be able to disable that setting setting "chromeWebSecurity": false in cypress.json but it will not work yet because you can only visit a single domain with cy.visit()

Attempt 2. Login programmatically from the test

Tried: login from the cypress test with auth0-js library so it is not needed to click in login button and thus no domain change occurs.

describe('Waiting to fetch', () => {
  beforeEach(() => {
    this.fetchAuthDeferred = getDeferred()
    cy.visit('http://localhost:3000', {
      onBeforeLoad(win) {
        cy.stub(win, 'fetch')
          .withArgs($url)
          .as('fetchAuth')
          .returns(this.fetchAuthDeferred.promise)
      }
    })
  })

  it('login', () => {
    cy.visit('http://localhost:3000')

    const auth = new auth0.WebAuth(authOptions)
    auth.login(loginOptions)

    cy.get('@fetchAuth', { timeout: 10000 }).should('haveOwnProperty', 'token')

    cy.visit('http://localhost:3000')
    cy.get('[class*="hamburger"]').click()
  })
})

Problems: cy.route() doesn't wait for fetch request, a workaround is to use cy.stub(win, 'fetch'). It won't wait:

enter image description here

Attempt 3. Login programmatically from the webapp

Tried: I started to think that cypress only spy request made from the app and not from the test itself (as I tried in the point above).

I added a fake-login button in the welcome page which will call auth0-js (so no domain change) with hardcoded credentials and click it from the test

cy.get('#fake-login').click()

Problems: that strategy worked, but of course I don't want to add a button with credential in the welcome page. So I tried adding the button element to the webapp during the test:

it('Login adding element', () => {
  cy.visit('http://localhost:3000')
  const = document.createElement('div')
  fakeLogin.innerHTML = 'Fake login'
  fakeLogin.onclick = function() {
    const auth = new auth0.WebAuth(authOptions)
    auth.login(loginOptions)
  }
  fakeLogin.style.position = 'absolute'
  fakeLogin.style.zIndex = 1000
  fakeLogin.id = 'fake-login'

  cy.get('#root').invoke('prepend', fakeLogin)
  cy.get('#fake-login').click()
  cy.get('[class*="hamburger"]').click() // Visible when logged in
})

And for some reason this doesn't work, the element is added but yt will not wait until the request are made.

So I don't know what else to try. Maybe everything is a misunderstanding of how login should be done in E2E, should I work with mock data so login is not needed?

Answer

Brandon picture Brandon · Sep 11, 2018

This is not currently supported in Cypress. I built a workaround that might help, though.

I set up a simple server that runs in parallel to cypress. The endpoint opens a headless instance of Puppeteer and completes the login flow, responding to the call with all the cookies:

const micro = require("micro");
const puppeteer = require("puppeteer");
const url = require("url");

const login = async (email, password) => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto("https://my-login-page.com");
  // do whatever you have to do to get to your auth0 lock screen, then:
  await page.waitFor(".auth0-lock-input-email");
  await page.waitFor("span.auth0-label-submit");
  await page.type(".auth0-lock-input-email input", email);
  await page.type(".auth0-lock-input-password input", password);
  await page.click("span.auth0-label-submit");
  await page.waitFor("some-selector-on-your-post-auth-page");
  return page.cookies();
 };

const server = micro(async (req, res) => {
  // expect request Url of form `http://localhost:3005?email=blahblah&password=blahblah
  const data = url.parse(req.url, true);
  const { email, password} = data.query;
  console.log(`Logging ${email} in.`);
  return login(email, password);
});

server.listen(3005);

Then I just extend Cypress to add the login command:

Cypress.Commands.add("login", (email, password) => {
  const reqUrl = `http://localhost:3005?email=${encodeURIComponent(
    email
  )}&password=${encodeURIComponent(password)}`;
  console.log("Beginning login.", reqUrl);
  cy.request(reqUrl).then(res => {
    const cookies = res.body;
    cookies.forEach((c) => {
      cy.setCookie(c.name, c.value, c);
    });
  });
});

Each call takes ~5-10s, which sucks, but better than not having any auth at all :/