problem accessing elements using id/name for login form in cypress - automation

I am trying to login to a form written in angular js but cypress throws the following exception:
Uncaught TypeError: $(...).materialScrollTop is not a function
This error originated from your application code, not from Cypress.
When Cypress detects uncaught errors originating from your application it will automatically fail the current test.
This behavior is configurable, and you can choose to turn this off by listening to the 'uncaught:exception' event.
https://on.cypress.io/uncaught-exception-from-application
This is the cypress login code:
context('TestLogin', () => {
it('Test Login', () => {
cy.visit('url');
cy.get('input[id=Email]').type('email', {force: true});
cy.get('input[id=Password]').type('passcode', { force: true });
cy.get('button[type=submit]').click();
})
})
Since the login has a csrf token, I have used cy.request() as follows and I do get a response with status code 200 but when re-loading the site it goes back to login page.
describe("Tests for AntiForgeryToken", function () {
// variable from config, that contain Identity Server URL
const identityUrl = Cypress.config("identityServerUrl")
// command declaration that we are going to use in tests
// allows us to create request to server
Cypress.Commands.add("loginByToken", function (token, login, password) {
cy.request({
method: "POST",
failOnStatusCode: false,
url: `${identityUrl}/Account/Login`,
form: true,
body: {
email: login,
password: password,
__RequestVerificationToken: token,
RememberLogin: false
}
})
})
it("Should parse token from response body and return 200", function () {
cy.request(`${identityUrl}/Account/Login`)
.its("body")
.then((body) => {
const $html = Cypress.$(body)
// when the page is rendered
// we are trying to find the Request Token in the body of page
const token = $html.find("input[name=__RequestVerificationToken]").val()
// POST request with token and login data
// then we simply verify whether Indentity Server authorized us
cy.loginByToken(token, "test#test.com", "Test_1234")
.then((resp) => {
expect(resp.status).to.eq(200)
})
})
cy.visit(`${identityUrl}/Account/`);
})
Cypress documentation didn't provide much info about the exception.
Any insights from cypress experts are helpful.

As evident from the error, Cypress is failing the test as it found an exception in your application,this is not a cypress level exception but an uncaught exception in your app which is causing cypress to fail the test, this is pretty useful as you can check if its an actual error in your app and log it for the dev team to fix, check if you are able to reproduce this manually, either way i think the application code should be fixed to either fix the bug or catch the exception and return a valuable error message. If you want to disable this feature you can turn off all uncaught exception handling, so in your index.js or whatever file is the entry point add the following:
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
// you can also add a Debugger here to analyze the error
debugger;
return false;
});
not sure if turning this off will help as looks like there is something in your application which could be an issue, but this is just for informational purposes that you can turn this feature off if you needed to.
Here is the documentation for further reading : Cypress Events documentation
hope this helps

Related

Playwright: unable to login via API setting cookie (able to do it with Cypress)

I'm trying to implemented login via API following Playwright's guidelines but somehow nothing seems to be working.
As a comparison I've built the same in Cypress and it works out of the box:
Context:
Playwright Version: 1.30
Operating System: Mac
Node.js version: v16.19.0
Browser: Chromium
I am unable to make a simple API login that works perfectly using Cypress instead. Let me share the 2 code snippets for comparison:
Simple test case:
API request to the login end-point - Auth token is retrieved
set the auth token as a cookie
navigate to a page that is accessible only if authenticated
Code Snippet
Cypress (working fine)
const body = {
username: 'username...',
password: 'password',
rememberMe: true,
};
describe('Login via API to management console', () => {
it('Login via API to management console', () => {
cy.request({
method: 'POST',
url: loginEndPoint,
headers: {
'Content-Type': 'application/json',
},
body,
}).then((response) => {
cy.setCookie('Authorization', `Token ${response.body.data.token}`);
});
cy.visit(`/management`);
});
});
Playwright (not working)
test('Login via API', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
const loginResponse = await context.request.post(`https://${process.env.MANAGEMENT_URL}/web/api/v2.1/users/login`, {
data: {
username: process.env.MANAGEMENT_USER,
password: process.env.MANAGEMENT_PASSWORD,
rememberMe: true,
}
});
const {
data: { token },
} = await loginResponse.body().then((b) => {
return JSON.parse(b.toString());
});
expect(token).toMatch(/^[a-z0-9]{80}$/)
await context.addCookies([{ name: 'Authorization', value: `Token ${token}`, path: '/', domain: `https://${process.env.MANAGEMENT_URL}` }]);
await page.goto(`https://${process.env.MANAGEMENT_URL}/management/`);
await expect(page).toHaveURL(/management/);
});
Describe the bug
Both scripts are successful at retrieving the authentication token but somehow either I'm doing something wrong with setting the cookie in Playwright or there is an issue. I'd assume the 2 scripts should be comparable.
Furthermore: I've tried to execute login via UI using global-setup, saving the storage-state, loading it before running the test and it fails also in this case... so there is something that is not setting properly the state in this case or the cookie in the previous one.
Not entirely sure why the cookie approach wasn’t working, perhaps the https:// part should be removed from the domain?
That being said, in Playwright you shouldn’t even need to do that especially within a single test, looking at the Playwright docs on signing in via the API and related page about the request context particularly under cookie management. The associated request and browser contexts share cookies, so once you complete the login request, the browser should already have the cookie state too and be logged in, so you should be able to just remove getting the token and adding the cookie. Or you can login with the API in the global setup even, as that doc showed. Just make sure in that case to save the storage state, and specify the same file in your config.
I see you tried the global setup approach (through the UI, but you can use the API since you have it), not sure what happened there. I would say to ensure that you specified the storageState in the config; I would be curious how you loaded it as mentioned, and if you’re still having problems maybe share the code you’re using for that piece?
Hope that helps or we can troubleshoot further!

Cypress uncaught:exception handler not working with Magic.link flow

I'm using Cypress to test a login flow that uses Magic.link auth on a mobile Web device, which is encountering the ResizeObserver loop limit exceeded error, as it tries to navigate the Google Auth forms. I've looked at numerous posts, and played around with my test, but it seems the handler is not working.
The recommended Google Authentication from the Cypress docs is insufficient, because with Magic, the flow is initiated by a call to magic.oauth.loginWithRedirect, hence I was hoping to drive the process via the UI directly.
You'll see I added a test to ensure the password input is visible. Now the exception is being thrown at that part of the test. If I remove that check the error occurs on the next step where I try to type the password.
describe('my auth flow', () => {
it('can auth with google', () => {
// click login button from my site
cy.get('button')
.contains('sign-in')
.click();
cy.origin('https://accounts.google.com', () => {
// enter email address
cy.get('input[type=email]')
.type('myuser#mydomain.com');
cy.get('button')
.find('span')
.contains('Next')
.click();
// wait for password page to show
cy.get('#password')
.should('exist')
.and('be.visible'); // error here...
// enter password
// error here if above visibility check removed
cy.get('#password input[type=password]')
.type('mypassword');
cy.get('button')
.find('span')
.contains('Next')
.click();
});
});
});
In support/commands.js, I've added the global error handler, which should handle all uncaught exceptions according to the documentation.
Cypress.on(
'uncaught:exception',
(err) => false
);
Magic does have a test mode, however I really don't want to bypass the login flow. Ideally I could exercise the login flow without hacks for testing.
The cy.origin() command is an isolated sandbox with different document and window to the primary domain.
Try adding the exception handler inside the origin command (presuming the error is happening while on the google domain).
cy.origin('https://accounts.google.com', () => {
Cypress.on('uncaught:exception', (err) => false)

Cypress auto submit on login page from beforeEach in second test

I'm working on a Cypress test for the Polish Wikipedia plugin, and I have this code in my cypress test:
Cypress.Commands.overwrite('visit', (orig, path, options) => {
return orig(`https://pl.wikipedia.org/${path}`);
});
Cypress.Commands.add('login', (pass) => {
cy.visit('/w/index.php?title=Specjalna:Zaloguj');
cy.get('#wpName1').type('<username>');
cy.get('#wpPassword1').type(pass);
cy.get('#userloginForm form').submit();
});
Cypress.Commands.add('dark_vector_wait', (pass) => {
cy.get('.vector-menu-content-list > li:nth-child(7)', { timeout: 10000 }).should('be.visible');
});
And in my spec:
describe('dark vector test', () => {
beforeEach('login', () => {
cy.login(Cypress.env('WIKI_PASSWORD'));
});
it('test discussion', () => {
cy.visit('/wiki/Dyskusja_wikipedysty:<username>');
cy.dark_vector_wait();
cy.matchImageSnapshot('discussion');
});
it('testing-page page', () => {
cy.visit('/wiki/Wikipedysta:<username>/testing-page');
cy.dark_vector_wait();
cy.matchImageSnapshot('testing-page');
});
});
And the second test is failing because as soon as Cypress type the password it automatically submits a form so cy.get('#userloginForm form').submit(); is executing after Cypress visits the home page (default redirect) and fail to find a form.
What's wrong? Why does Cypress auto-submit a form after a second time? This is not what Wikipedia is doing since the login form doesn't have any JavaScript code and you need to click login to be able to login to Wikipedia.
EDIT:
I've tried to
Use BaseURL and remove overwrite of visit.
Add type('{enter}'), but this only shows an error: cy.type() failed because this element is detached from the DOM.
EDIT 2
This is the screenshot of the action taken by cypress, second test has a page load without {enter} action and without form submit.
The problem is in Cypress.Commands.overwrite('visit').
You pass the parameter with a leading slash '/wiki/Dyskusja_wikipedysty:<username>' but concatinate to base which also has a trailing slash https://pl.wikipedia.org/${path}, so now the full path is
https://pl.wikipedia.org//wiki/Dyskusja_wikipedysty:<username>
If you set baseUrl in configuration, Cypress sorts it out for you
cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: 'https://pl.wikipedia.org'
}
})
Then remove the Cypress.Commands.overwrite('visit').
With these changes, I was able to pass both tests.
Using cy.session()
The problem might be one specific to locality, I do not have any steps missing in the Cypress log.
You can try adding a session cache so that the first login is re-used.
Cypress.Commands.add('login', (pass) => {
cy.session('login', () => { // login occurs only once
// credentials are cached
cy.visit('/w/index.php?title=Specjalna:Zaloguj');
cy.get('#wpName1').type('Jack Bosko');
cy.get('#wpPassword1').type(pass);
cy.get('#userloginForm form').submit();
// for good measure, confirm login was successful
// by checking for your name on the page
cy.contains('span', 'Jack Bosko')
})
})
So the problem was the weird IME keyboard that is part of MediaWiki. I somehow got it enabled on my system even when I was not logged in. Maybe added globally with cookies or something.
I noticed that keyboard when I was asking questions on the MediaWiki support page.
This is not related to Cypress. I'm investigating why the keyboard is there, and why clean environment it.

cypress cy.request 401 unauthorized [duplicate]

I want to save/persist/preserve a cookie or localStorage token that is set by a cy.request(), so that I don't have to use a custom command to login on every test. This should work for tokens like jwt (json web tokens) that are stored in the client's localStorage.
To update this thread, there is already a better solution available for preserving cookies (by #bkucera); but now there is a workaround available now to save and restore local storage between the tests (in case needed). I recently faced this issue; and found this solution working.
This solution is by using helper commands and consuming them inside the tests,
Inside - cypress/support/<some_command>.js
let LOCAL_STORAGE_MEMORY = {};
Cypress.Commands.add("saveLocalStorage", () => {
Object.keys(localStorage).forEach(key => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});
Cypress.Commands.add("restoreLocalStorage", () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});
Then in test,
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
Reference: https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888
From the Cypress docs
For persisting cookies: By default, Cypress automatically clears all cookies before each test to prevent state from building up.
You can configure specific cookies to be preserved across tests using the Cypress.Cookies api:
// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
preserve: "session_id"
})
NOTE: Before Cypress v5.0 the configuration key is "whitelist", not "preserve".
For persisting localStorage: It's not built in ATM, but you can achieve it manually right now because the method thats clear local storage is publicly exposed as Cypress.LocalStorage.clear.
You can backup this method and override it based on the keys sent in.
const clear = Cypress.LocalStorage.clear
Cypress.LocalStorage.clear = function (keys, ls, rs) {
// do something with the keys here
if (keys) {
return clear.apply(this, arguments)
}
}
You can add your own login command to Cypress, and use the cypress-localstorage-commands package to persist localStorage between tests.
In support/commands:
import "cypress-localstorage-commands";
Cypress.Commands.add('loginAs', (UserEmail, UserPwd) => {
cy.request({
method: 'POST',
url: "/loginWithToken",
body: {
user: {
email: UserEmail,
password: UserPwd,
}
}
})
.its('body')
.then((body) => {
cy.setLocalStorage("accessToken", body.accessToken);
cy.setLocalStorage("refreshToken", body.refreshToken);
});
});
Inside your tests:
describe("when user FOO is logged in", ()=> {
before(() => {
cy.loginAs("foo#foo.com", "fooPassword");
cy.saveLocalStorage();
});
beforeEach(() => {
cy.visit("/your-private-page");
cy.restoreLocalStorage();
});
it('should exist accessToken in localStorage', () => {
cy.getLocalStorage("accessToken").should("exist");
});
it('should exist refreshToken in localStorage', () => {
cy.getLocalStorage("refreshToken").should("exist");
});
});
Here is the solution that worked for me:
Cypress.LocalStorage.clear = function (keys, ls, rs) {
return;
before(() => {
LocalStorage.clear();
Login();
})
Control of cookie clearing is supported by Cypress: https://docs.cypress.io/api/cypress-api/cookies.html
I'm not sure about local storage, but for cookies, I ended up doing the following to store all cookies between tests once.
beforeEach(function () {
cy.getCookies().then(cookies => {
const namesOfCookies = cookies.map(c => c.name)
Cypress.Cookies.preserveOnce(...namesOfCookies)
})
})
According to the documentation, Cypress.Cookies.defaults will maintain the changes for every test run after that. In my opinion, this is not ideal as this increases test suite coupling.
I added a more robust response in this Cypress issue: https://github.com/cypress-io/cypress/issues/959#issuecomment-828077512
I know this is an old question but wanted to share my solution either way in case someone needs it.
For keeping a google token cookie, there is a library called
cypress-social-login. It seems to have other OAuth providers as a milestone.
It's recommended by the cypress team and can be found on the cypress plugin page.
https://github.com/lirantal/cypress-social-logins
This Cypress library makes it possible to perform third-party logins
(think oauth) for services such as GitHub, Google or Facebook.
It does so by delegating the login process to a puppeteer flow that
performs the login and returns the cookies for the application under
test so they can be set by the calling Cypress flow for the duration
of the test.
I can see suggestions to use whitelist. But it does not seem to work during cypress run.
Tried below methods in before() and beforeEach() respectively:
Cypress.Cookies.defaults({
whitelist: "token"
})
and
Cypress.Cookies.preserveOnce('token');
But none seemed to work. But either method working fine while cypress open i.e. GUI mode. Any ideas where I am coming short?
2023 Updated on Cypress v12 or more:
Since Cypress Version 12 you can use the new cy.session()
it cache and restore cookies, localStorage, and sessionStorage (i.e. session data) in order to recreate a consistent browser context between tests.
Here's how to use it
// Caching session when logging in via page visit
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('form').contains('Log In').click()
cy.url().should('contain', '/login-successful')
})

Cypress doesn't work with an external login

I'm working on e2e test with cypress on my application.
In my case the login are manage by a external service.
When I want to enter in my application's home page (https://myApplication/home), the system redirects me in different superdomains to login.
At first cypress seems to be able to change the superdomain, but once arrived in external service page for the authentication, the system go in login error (as if we have already logged in, but incorrect).
This type of behavior does not happen outside the cypress .
Are there alternative solutions to manage external access in a cypress test or is it possible to manage it directly from cypress?
I added in my cypress.json the chromeWebSecurity:false and when I call the link for login, I added the failOnStatusCode: false,
but it still doesn't work.
Assuming this is caused by SameSite cookie blocking , then I've just been fighting the same issue. I resolved it by intercepting all requests, checking if they had a set-cookie header(s) and rewriting the SameSite attribute. There's probably a neater way to do it, as this does clutter up the cypress dashboard a little.
Sadly Zachary Costa's answer no longer works as Chrome 94 removed the SameSiteByDefaultCookies flag.
You can add this as a command for easy reuse:
In your commands file:
declare namespace Cypress {
interface Chainable<Subject> {
disableSameSiteCookieRestrictions(): void;
}
}
Cypress.Commands.add('disableSameSiteCookieRestrictions', () => {
cy.intercept('*', (req) => {
req.on('response', (res) => {
if (!res.headers['set-cookie']) {
return;
}
const disableSameSite = (headerContent: string): string => {
return headerContent.replace(/samesite=(lax|strict)/ig, 'samesite=none');
}
if (Array.isArray(res.headers['set-cookie'])) {
res.headers['set-cookie'] = res.headers['set-cookie'].map(disableSameSite);
} else {
res.headers['set-cookie'] = disableSameSite(res.headers['set-cookie']);
}
})
});
});
Usage:
it('should login using third party idp', () => {
cy.disableSameSiteCookieRestrictions();
//add test body here
});
or alteratively, run it before each test:
beforeEach(() => cy.disableSameSiteCookieRestrictions());
We were encountering a similar issue, where Cypress was redirecting us to the default "You are not logged in" page after getting through the login process. I'm not certain if that's EXACTLY the issue you were experiencing, but just in case, here's our solution. In our case, the issue was caused by Chrome's "Same Site Cookies" feature interacting poorly with Cypress, so we needed to disable it. In your plugins/index.js file, you would add the following code:
module.exports = (on, config) => {
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-features=SameSiteByDefaultCookies');
}
return launchOptions;
});
};
Note that if you already have launchOptions being set, you can just add this code onto it so it doesn't clash at all.
Hopefully, this works for you as well!
In the current version of cypress you can't go to another domain in the same test. This is due to the fact that cypress injects its test into the browser (they are working on this issue).
So one solution today is that you need to utilize cy.request to perform the login programmatically and inject the auth secret (jwt, cookie, localstorage, token or what you have) into the browser context yourself (for cookie this would be cy.setcookie).
Always make sure to checkout the plugins if there is already an abstraction for your login. Often this is openId or ntlm.