Cypress.Cookies.preserveOnce('session_id') isn't working when called in afterEach hook - mocha.js

I'm working with cypress tests and I want to avoid having to log in before each test. so, I wanted to preserve the cookies in each test file.
The log statement in the afterEach hook is triggered, however cookies are cleared in the second testcase.
describe('Users Page Scenarios', () => {
before(() => {
myApp.pages.Login.navigate();
myApp.pages.Login.login(
credentials.globalAdmin.email,
credentials.globalAdmin.password
);
});
beforeEach('navigate to users page before each test', () => {
myApp.sharedComponents.Header.navigateToUsers();
});
afterEach(() => {
Cypress.Cookies.preserveOnce('session_id');
cy.log('test');
});
describe('Users List', () => {
it('Should redirect the user to users page after clicking on users in the navigation header', () => {
cy.url().should('eq', `${Cypress.config().baseUrl}/user`);
});
})
describe('New User Creation', () => {
it('Should open new user modal after clicking on invite administrator', () => {
myApp.pages.Users.UsersList.inviteAdministrator();
cy.url().should('eq', `${Cypress.config().baseUrl}/user/new`);
});
it('Should create a new user successfully', () => {
myApp.pages.Users.UsersList.inviteAdministrator();
myApp.pages.Users.UsersInfo.createNewUser(user.generateUser());
})
});

The docs indicate that Cypress.Cookies.preserveOnce('session_id') is used in beforeEach().
Looks like after is too late.
describe('Dashboard', () => {
before(() => {
// log in only once before any of the tests run.
// your app will likely set some sort of session cookie.
// you'll need to know the name of the cookie(s), which you can find
// in your Resources -> Cookies panel in the Chrome Dev Tools.
cy.login()
})
beforeEach(() => {
// before each test, we can automatically preserve the
// 'session_id' and 'remember_token' cookies. this means they
// will not be cleared before the NEXT test starts.
//
// the name of your cookies will likely be different
// this is an example
Cypress.Cookies.preserveOnce('session_id', 'remember_token')
})
If you have localStorage or sessionStorage to preserve, or you have not identified all cookies correctly, try with cy.session()
beforeEach(() => { // must be beforeEach()
cy.session('mySession', () => { // preserves localStorage, sessionStorage, cookies
myApp.pages.Login.navigate();
myApp.pages.Login.login(...); // only called once (despite beforeEach())
})
})

Related

When I want to switch from A tab to B then it shows blank page using cypress

After login when I switch from Tab A to Tab B then it shows blank page,
How i maintain session of Tab A. After performing activity on Tab A, I move to Tab B and start performing activity on Tab B
If the session has not been created yet, it will run the callback fn code, otherwise, it will only restore the session (and you'll have to visit the page again).
So, I think changing before hook to a beforeEach and adding a visit after the login method would work:
beforeEach(() => {
login('user1')
cy.visit('http://#')
})
But this approach will visit the page twice in the first run, to avoid this personally I would use the login with it's code in the before hook and restore and visit the page in the beforeEach.
const login = ({ sessionId, username, password }) => {
cy.session(sessionId, () => {
cy.visit('http://#')
cy.get('[type=text]').type(username)
cy.get('[type=password]').type(password)
cy.get('[type=submit]').click()
})
}
describe('test', () => {
const sessionId = 'Login with valid credentials'
before(() => {
login({ sessionId, username: 'user1', password: 'Test123' })
})
beforeEach(() => {
cy.session(sessionId)
cy.visit('http://#')
})
it('Tab A', () => {
cy.get('#A').click()
})
it('Tab B', () => {
cy.get('#B').click()
})
})
Please let me know if it solves your problem.

Cypress using actions from Pinia Vue3

I was learning some cypress from this video: https://www.youtube.com/watch?v=03kG2rdJYtc
I'm interested with he's saying at 29:33: "programatic login"
But he's using vue2 and Vuex.
My project is created with Vite and the state management is Pinia.
So how can I do a programatic login using the pinia action?
For example the welcome logged in user should see dashboard:
describe('Welcome', () => {
it('logged in user should visit dashboard', () => {
// login
cy.visit('/')
cy.url().should('contain', '/dashboard')
})
})
And my userStore:
export const useUserStore = defineStore({
id: 'user',
state: () => ({
username: ref(useLocalStorage('username', null)),
}),
getters: {
isLoggedIn: (state) => state.username !== null,
},
actions: {
login(username, password) {
return useAuthLoginService(username, password)
.then((response) => {
this.username = response.username
})
.catch((error) => {
return Promise.reject(new Error(error))
})
},
},
})
How can I call the login action on the cypress test?
For now as a workaround I'm writing on a localstorage like:
localStorage.setItem('username', 'user')
And it works fine, because userStore catch this item from localstorage and passes like it's logged in... But I don't like this solution, seems fragile, and I'd like to use the action which is made for login users.
Another thing I tried is adding the app variable inside window but it doesn't work for me... don't understand why...
on main.js
The video shows that code:
const vue = new Vue({...})
if(window.Cypress){
window.app = app
}
In my case it's:
const app = createApp(App)
if(window.Cypress){
window.app = app
}
But in cypress tests the window.app it's undefined... I don't know how I would access to userStore using this... like it was vuex.
Using the Pinia demo app as an example:
The store is initialized in App.vue. Add a reference to the newly created store(s) for Cypress to use
export default defineComponent({
components: { Layout, PiniaLogo },
setup() {
const user = useUserStore()
const cart = useCartStore()
if (window.Cypress) {
window.store = {user, cart) // test can see window.store
}
...
In the test
let store;
describe('Pinia demo with counters', () => {
beforeEach(() => {
cy.viewport(1000, 1000)
cy.visit(`http://localhost:${PORT}`)
.then(win => store = win.store) // get app's store object
})
it('works', () => {
cy.wait(500) // wait for the JS to load
.then(() => store.cart.addItem('Cypress test item')) // invoke action
.then(() => {
const item1 = store.cart.items[0] // invoke getter
cy.wrap(item1)
.should('have.property', 'name', 'Cypress test item') // passes
})
The login action is asynchronous, so return the promise to allow Cypress to wait.
// user.js
async login(user, password) {
const userData = await apiLogin(user, password)
this.$patch({
name: user,
...userData,
})
return userData // this returns a promise which can awaited
},
// main.spec.js
describe('Pinia demo with counters', () => {
beforeEach(() => {
cy.viewport(1000, 1000)
cy.visit(`http://localhost:${PORT}`).then(win => {
store = win.store
// default name in store before login
cy.wrap(store.user.name).should('eq', 'Eduardo')
// logging in
store.user.login('ed', 'ed').then(() => { // wait for API call
cy.wrap(store.user.name).should('eq', 'ed')
})
})
})
Alternatively, wait for the name to change on the page
// main.spec.js
cy.visit(`http://localhost:${PORT}`).then(win => {
store = win.store
// default name in store
cy.wrap(store.user.name).should('eq', 'Eduardo')
// logging on
store.user.login('ed', 'ed')
cy.contains('Hello ed') // waits for name on page to change
.then(() => {
cy.wrap(store.user.name).should('eq', 'ed')
})
})

Cypress with Auth0.com login and redirects

We have changed our login page so it's now redirecting to auth0.com and then back to our domain after you login to auth0. The issue is now when I login I get redirected to our QA environment which requires authentication so once the test submits the form I get a 401.
Before auth0 I was getting around the 401 by overwritting the visit function passing in a auth header.
If I try to visit our QA environment first before going to our login page I get
You may only cy.visit() same-origin URLs within a single test.
I've seen other questions asked about auth0 but not with also requiring authentication in the redirect, is it possible to still run tests on our QA environment?
While the Cypress team works to resolve max 1 site... support visiting multiple superdomains in one test on work around I have used in the past is this: detailed again below.
In commands.js
// -- Save localStorage between tests
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]);
});
});
// -- Visit multiple domains in one test
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
In test.spec.js
/// <reference types="cypress" />
context('Force Visit', () => {
it('should be able to visit and assert on two domains', () => {
cy.forceVisit('https://example.cypress.io');
cy.title().should('eq', 'Cypress.io: Kitchen Sink');
cy.forceVisit('https://www.google.com');
cy.title().should('eq', 'Google');
});
});
context('Auth Flow', () => {
before(() => {
cy.forceVisit('<auth url>');
cy.get('<username input>').type('<username>');
cy.get('<password input>').type('<password>');
cy.intercept('POST', '<auth login request>').as('auth');
cy.get('<submit button>').click();
cy.wait('#auth');
});
afterEach(() => {
cy.saveLocalStorage();
});
beforeEach(() => {
cy.restoreLocalStorage();
// Preserve Cookies between tests
Cypress.Cookies.defaults({
preserve: /[\s\S]*/,
});
});
it('should be able to start in authorized state', () => {
cy.visit('<site url>');
});
});

How can I close the browser after a mocha test?

I am new to WebdriverIO and Mocha and I wrote 2 tests in order to check our web app.
I want to close the browser and sign-in again, after I run the first test.
When I used browser.close(), I got an error the browser.close() is not a function and basically the second test runs right after the first test with the browser being open.
Is there a way to close to browser after a test in Mocha?
describe('Verify that a signed-in user can get to the page', () => {
it('Title assertion ', () => {
const viUrl = 'https://buyermanage.com/bmgt/lock?useMock=pre.json';
signInPage.signIn('rich221', 'password', viUrl);
assert.equal(preApp.getTitle(), 'Pre-App', 'Title Mismatch');
});
});
describe("Verify that a not signed-in user can't get to the page and is redirected to login page", () => {
it('Title assertion ', () => {
const viUrl = 'https://buyermanage.com/bmgt/lock?useMock=pre.json';
assert.equal(preApp.getTitle(), 'Pre-App', 'Title Mismatch');
});
});
Try using browser.reloadSession():
after(() => {
// Start a new session for every 'locale' (wipe browser cache):
browser.reloadSession();
});
In your particular example, you'd need to use it in the afterEach() hook, and wrap the describes, respectively the it statements (depending on your test-suite requirements) inside a parent describe block:
describe('The world is your oister', () => {
describe('Verify that a signed-in user can get to the page', () => {
it('Title assertion ', () => {
// bla bla bla
});
});
describe("Verify that a not signed-in user can't get to the page and is redirected to login page", () => {
it('Title assertion ', () => {
// bla bla bla
});
});
afterEach(() => {
browser.reloadSession();
});
});

having multiple describe in spec leads to weird behaviour

I have 2 describe blocks in a spec file.
First, describe visits xyz.com and Second, describe visits abc.com
And I need these 2 describe in one spec only. The wired behavior I see is it runs the tests smoothly but after visiting abc.com from 2nd describe it starts running 1st describe again. An infinite loop of tests
var signedOutArtifactID = null;
describe('WEB APP E2E tests', function() {
var token = null;
before(function() {
cy.visit('/');
// Login
cy.get('#username')
.type(auth.geneticist.username);
cy.get('#password')
.type(auth.geneticist.password);
cy.get('button')
.contains('Login')
.click()
.should(function() {
token = localStorage.getItem('token');
expect(token).not.to.be.null;
});
});
beforeEach(function() {
localStorage.setItem('token', token);
cy.contains('Logout')
.should('exist');
expect(localStorage.getItem('token'));
});
it('should land on home page', function() {
cy.url()
.should('include', '/home');
});
it('should save and generate and end up on signout page', function() {
cy.contains('Save and Generate Report')
.click();
cy.url()
.should('include', '/sign-out');
});
it('should signout and send successfully', function() {
cy.url()
.should(function(currentURL) {
signedOutArtifactID = currentURL.match(/2-([0-9]+)/)[0];
expect(signedOutArtifactID).not.to.be.null;
});
// Make sure interpretation was updated
cy.get('.card-body pre')
.should('contain', 'test interpretation added by cypress');
cy.contains('Sign Out and Send')
.click();
cy.contains('Yes, sign out and send')
.click();
});
});
describe('2nd WEB APP E2E tests', function() {
before(function () {
cy.visit({
url:`https://webappurl.com/search?scope=All&query=${signedOutArtifactID}`,
failOnStatusCode: false
})
})
it('Review Completed step in clarity', async () => {
cy.get('#username').type(auth.clarity_creds.username)
cy.get('#password').type(auth.clarity_creds.password)
cy.get('#sign-in').click()
cy.get('.result-name').click()
cy.get('.view-work-link').contains('QWERTYU-IDS').click()
cy.get('.download-file-link ')
.should(($downloads) => {
expect($downloads).to.have.length(2)
})
});
});
describe defines a test suite. You can only have one top-level test suite per file, and only one domain per test.
I would just change your describes to contexts and wrap both contexts in a single describe, like so:
var signedOutArtifactID = null;
describe('e2e tests', function() {
context('WEB APP E2E tests', function() {
var token = null;
before(function() {
cy.visit('/');
// Login
cy.get('#username')
.type(auth.geneticist.username);
cy.get('#password')
.type(auth.geneticist.password);
cy.get('button')
.contains('Login')
.click()
.should(function() {
token = localStorage.getItem('token');
expect(token).not.to.be.null;
});
});
beforeEach(function() {
localStorage.setItem('token', token);
cy.contains('Logout')
.should('exist');
expect(localStorage.getItem('token'));
});
it('should land on home page', function() {
cy.url()
.should('include', '/home');
});
it('should save and generate and end up on signout page', function() {
cy.contains('Save and Generate Report')
.click();
cy.url()
.should('include', '/sign-out');
});
it('should signout and send successfully', function() {
cy.url()
.should(function(currentURL) {
signedOutArtifactID = currentURL.match(/2-([0-9]+)/)[0];
expect(signedOutArtifactID).not.to.be.null;
});
// Make sure interpretation was updated
cy.get('.card-body pre')
.should('contain', 'test interpretation added by cypress');
cy.contains('Sign Out and Send')
.click();
cy.contains('Yes, sign out and send')
.click();
});
});
context('2nd WEB APP E2E tests', function() {
before(function () {
cy.visit({
url:`https://webappurl.com/search?scope=All&query=${signedOutArtifactID}`,
failOnStatusCode: false
})
})
it('Review Completed step in clarity', async () => {
cy.get('#username').type(auth.clarity_creds.username)
cy.get('#password').type(auth.clarity_creds.password)
cy.get('#sign-in').click()
cy.get('.result-name').click()
cy.get('.view-work-link').contains('QWERTYU-IDS').click()
cy.get('.download-file-link ')
.should(($downloads) => {
expect($downloads).to.have.length(2)
})
});
});
})
There should be one describe block per suite (specification file). Therefore, when I need to wrap multiple related tests in one specification file I use context. Also, the following is what cypress documentation says:
The test interface, borrowed from Mocha, provides describe(),
context(), it() and specify().
context() is identical to describe() and specify() is identical to
it(), so choose whatever terminology works best for you
However, I believe the test structure and describe, context, it hierarchy is a little off track. So, here is how I write tests:
describe('User Authentication Using Custom Auth Token', () => {
beforeEach(() => {
cy.on('uncaught:exception', err => {
console.log('cypress has detected uncaught exception', err);
return false;
});
});
context('when not authenticated', () => {
it('Redirects to /login and Stays on /Login', () => {
cy.visit('/');
cy.location('pathname').should('equal', '/login');
// more on your logic
});
context('when authenticated', () => {
it('Successful login using Custom Auth Token', ()=>{
cy.visit('/')
cy.login();
// more on your logic
});
});
});

Resources