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.
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:
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?
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 :/