Navigate into 2 pages in the same test - javascript

I'm very new in Javascript and Cypress so pardon me if it's something very simple.
What I would like to do is to navigate into 2 pages inside one test.
My test.cy.js file has this code:
describe('Sitop Manager Login Page', () => {
it('opens login page, fill the form and clicks submit', () => {
cy.visit('/login')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.contains('Login').click()
})
it('Open devices page', () => {
cy.visit('/devices')
cy.wait(500)
cy.get('ix-menu ix-menu-item[tab-icon=cogwheel]').click() //css selector
})
})
The issue is that when it finishes the /login page, then it reloads to login page again so the /devices page cannot be tested. In my cypress.config.js file i have setted the baseUrl
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000', //Baseurl to be used in all accross the testing classes
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

If /devices is a "login required" route, then the first step in any test that accesses it must be to log in. So you will always need to visit /login first, go through the login workflow, then you can visit /devices.
it('Open devices page', () => {
cy.visit('/login')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('admin')
cy.contains('Login').click()
cy.wait(500)
cy.visit('/devices')
cy.get('ix-menu ix-menu-item[tab-icon=cogwheel]').click() //css selector
})
The wait after logging in and before visiting /devices may not be necessary, depending on the default timeout settings for your testing environment.
As #jjhelguero mentioned in the comments, you can also intercept a network request (perhaps to your login endpoint) and wait for it to return successfully as an alternative to the wait. get will retry, but could timeout: https://docs.cypress.io/api/commands/intercept
There are things you can do to avoid needing to actually log in, like setting a session cookie, but probably you want to emulate the actual user workflow in your tests.

Related

userRole loads white page after first test unless preserverURL: true is used, preventing navigating directly to a page with .page

When setting up a test the .page method won't visit a page or the page will be white because of a failure with the authentication process with userRole. The only fix so far has been to add preserveURL: true the issue with this is the tests are taking a substantially longer time as the test must then navigate to the proper page.
Every post and docs I've read say this should just work, I'm hoping someone here can point me in the right direction or offer some things I can try.
Adding it all in one file, but each is split into their own file.
// authentication file .js
import { Role } from "testcafe";
const userLogins = require('../logins.json');
let users = [];
let passwords = [];
userLogins.forEach( data => {
users.push(data.name);
passwords.push(data.password);
})
const admin = Role('https://foo.example.com/', async t => {
await t
.typeText('#email', users[0], { paste: true, replace: true })
.typeText('#password', passwords[0], { paste: true, replace: true })
.click('#login-btn');
}); // adding the option { preserveURL: True } here will cause all tests to pass successfully if I add code to each test to nav to the correct page
// and disable .page in the test spec
export { admin };
// page model file .js
import { Selector, t } from "testcafe";
class FooPage {
constructor() {
this.searchInput = Selector('#searchInput');
this.orderCount = Selector('#orderNumber');
this.businessName = Selector('#businessName');
this.contactNumber = Selector('#contactNumber');
};
async searchResults(selector, searchText) {
// enter search term
await t
.typeText(this.searchInput, searchText, {paste: true, replace: true})
.pressKey('enter');
// check how many rows were returned
const rowCount = await this.orderCount.count;
let searchResults = []
// verify all rows returned contain only our search text
for (let i = 0; i < rowCount; i++) {
let text = await selector.nth(i).innerText;
searchResults.push(text);
await t.expect(searchResults[i]).contains(searchText);
}
}
export default FooPage;
// test spec file .js
import { admin } from "../authentication";
import FooPage from "../FooPage";
const fooPage = new FooPage();
fixture `Test searching foo orders`
.page`https://foo.example.com/#/foo_orders` // this works for first test then loads white page thereafter
.beforeEach( async t => {
await t
.resizeWindow(1284, 722)
.useRole(admin)
});
// this test will work correctly
test(`User can search via order number`, async t => {
await fooPage.searchResults(fooPage.orderCount, 'FOO111');
});
// this test will load a white page and fail
test(`User can search via business purchaser`, async t => {
await fooPage.searchResults(fooPage.businessName, 'Foo Company');
});
// this test will load a white page and fail
test(`User can search via phone number`, async t => {
await fooPage.searchResults(fooPage.contactNumber, '555-555-5555');
});
I won't be able to post an example site as it's all proprietary. This will work though if I remove the .page and add in preserveUrl: true into the authentication file. The spec, page model and authentication file all clearly work.
But, I can't use .page to navigate directly to the page I want. This is increasing test times and breaking the cardinal rule of navigating directly to the page to test.
Your code is correct. It looks like your app has some specifics, which do not allow it to work correctly without the preserveUrl option.
However, it's difficult to determine the cause of the issue without a working example. Since you can't share an example here, I would ask you to send it to the official TestCafe support email: support#devexpress.com
Please note that the DevExpress TestCafe policy prevents us (TestCafe Team) from accessing internal resources without prior written approval from a site owner. If we need to access non-public parts of your website or pass through authorization pages, please ask the website owner to send us (support#devexpress.com) a written confirmation. It must permit DevExpress personnel to remotely access the website and its internal resources for research/testing/and debugging purposes.

Cypress Testing : How to preserve access_token/cookies/sessions?

In my application, when I run the application, It redirects to Github Login to access the repository from the account. When the Sign In button is clicked, I get this message.
Cookies must be enabled to use GitHub.
So can anyone tell me what should I do. I tried using Cypress.Cookies.preserveOnce() , but it doesn't work.
Also, we get an access_token after successful login.
describe('Login Page', () => {
beforeEach(() => {
Cypress.Cookies.preserveOnce('access_token', 'value for the access_token')
})
it('successfully logged In', () => {
cy.get('#login_field').type('username')
cy.get('#password').type('password')
cy.get('.btn-block').click()
})
})
Take a look at this [Cypress Social Media Logins][1]. I think you are using Github Oauth to login and access.
You have to login using the plugin GitHubSocialLogin. Save the cookie that you get in the localStorage.
You will have to explore further on these lines to suit your particular use case.
[1]: https://github.com/lirantal/cypress-social-logins

Cypress login using request method

I register & login a user, however, when in my test I navigate to a page behind authentication, Cypress fails & takes me back to the login page. From the looks of it, the before function is successfully executed (as verified by the API log). Here is my code:
describe("Dashboard page", () => {
before(() => {
cy.fixture("authUserRegistrationDetail.json").then(userDetail => {
cy.fixture("authUserLoginDetail.json").then(userLoginDetail => {
cy.visit("http://localhost:3000/login");
cy.get(".cookieConsent button").click();
// create a random email for registration
userDetail.email = `${Math.random()
.toString(36)
.slice(-5)}#aaa.aaa`;
// share the email between userLogin & userRegistration obj
userLoginDetail.email = userDetail.email;
// register the user
cy.request("POST", "http://localhost:9000/users/", userDetail)
.its("body")
// login the same user
cy.request("POST", "http://localhost:9000/api-token-auth/", userLoginDetail).then($res => {
cy.request({
url: "http://localhost:9000/loggedinuser/",
headers: {
Authorization: `Token ${$res.body.token}`
}
});
});
});
});
});
// run the test
it("visits the dashboard...", () => {
cy.visit("http://localhost:3000/dashboard/");
cy.get("h2").contains("Your deals");
});
});
Once the code is run, the test fails on assertion and the user is not logged in. Here is the screenshot of the test result. I get a status code 200 when user signs up & then logs in. Why is the user login not persisting in the tests & the dashboard link fails.
EDIT:
I just realised that I am programmatically logging in, however, once logged in, how do I get Cypress browser to recognise the change in state & that the user is logged in. I.e, how do I refresh the Cypress screen to recognise the the user login?
From the above code, it doesn't look like you are preserving the cookie once logged in. Cypress automatically clears all cookies before each test to prevent state from building up. You should be able to do something similar to this:
before(() => {..cy.login() })
beforeEach(() => {
Cypress.Cookies.preserveOnce('session_id', 'remember_token')
})
This cypress doco should provide more context https://docs.cypress.io/api/cypress-api/cookies.html#Preserve-Once

Page visibility event not stability fired in Vue PWA

I'm building a Vue PWA with Firebase authentication. The web app will listens Firebase's onAuthStateChanged event on App first loaded to automatically sign the user in and save his ID token for API requests latter, by invoke Firebase's getIdToken(/* forceRefresh */ true).
Beside that, I also utilize Page Visibility API to reload the Web App after 5 minutes hidden (to get new Firebase ID token if the old one has expired).
On my Android phone, I visit my web app URL on Chrome, then add icon to home screen and make all test cases by access the web app thru that icon.
Here is the test case: after sign in and using the web app normally, I click Home button to hide the web app, then after ~10 minutes, I recall the app from background state, the web app was auto-reload successfully then I could continue using it as normal. The problem is, if I recall the app from background after a long time (~6 hours), the web app do not auto-reload then I don't have new Firebase ID Token of the user, as a result I get Token Expired error when making API request to get user profile...
I need to findout a reliable way to trigger autoLogin() function, so users don't need to re-login every time when they come back using my WebApp.
Here are skeleton code base:
main.js
const unsubscribe = fibAuth.onAuthStateChanged((user) => {
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
created () {
// Firebase auto login
if (user) {
store.dispatch('autoLogin', user)
}
// Reload after a duration
document.addEventListener('visibilitychange', function () {
store.dispatch('appVisibilityHandler', document.visibilityState)
})
} // end created()
}) // end Vue()
unsubscribe()
})
Vue Store - index.js
async autoLogin ({commit, state, dispatch}, userPayload) {
commit('SET_APP_LOADING', true)
try {
let idToken = await userPayload.getIdToken(/* forceRefresh */ true)
console.warn('store::autoLogin() - idToken:', idToken)
let apiResponse = await UsersRepos.getMyProfile(idToken)
// ... processing apiResponse ...
} catch (error) {
console.error('store::autoLogin() - error:', error)
}
commit('SET_APP_LOADING', false)
},
appVisibilityHandler (context, statePayload) {
try {
const APP_REFRESH_SECONDS_THRESHOLD = 300 // 5 minutes
if (statePayload === 'hidden') {
localStorage.setItem('app-hidden-ts', (new Date()).getTime())
} else if (statePayload === 'visible') {
let lastSec = parseInt(localStorage.getItem('app-hidden-ts') / 1000)
let nowSec = parseInt((new Date()).getTime() / 1000)
localStorage.setItem('app-hidden-ts', nowSec * 1000)
console.warn('total hidden seconds:', (nowSec - lastSec))
if (nowSec - lastSec > APP_REFRESH_SECONDS_THRESHOLD) {
context.commit('SET_APP_LOADING', true)
// refresh the whole web page
router.go()
}
}
} catch (error) {
alert('appVisibilityHandler error:' + error.message)
}
}
I really appreciate any guide or clue to overcome the issue. Thank you in advance!
Firebase Authentication uses ID tokens that are valid for an hour. Calls to getIdToken() return this token. The SDK automatically refreshes the ID token in the background, but of course can't recall your autoLogin since the authentication state didn't change.
You'll want to attach an onIdTokenChanged handler to detect when the ID token has changed and pick it up.
firebase.auth().onIdTokenChanged(function(user) {
if (user) {
// User is signed in or token was refreshed.
store.dispatch('autoLogin', user)
}
});
In fact, this might replace your onAuthStateChanged handler, since this also fires when the user signs in.

Stay logged in when using msal.js

I'm building a small JS app for my Microsoft ToDo tasks and use the msal.js library to accommodate the authentication process.
This works perfectly fine, I get a popup, I authenticate my profile, the popup closes and my tasks appear on my screen.
But: It doesn't seem to remember that I authenticated before; Every time I run my webpack app and the page is booted it shows the popup and asks for authentication. Once I've authenticated and just refresh my page, it just shows me the tasks without showing the popup again. I haven't tried waiting for an hour but I think it has something to do with not properly refreshing my access token. I'm not that involved with the Outlook/Microsoft API that I can really figure out if I'm using it correctly.
In short: How can I authenticate once so that the next time I start my app the tasks are shown without having to authenticate again?
My init function
this.userAgentApplication = new Msal.UserAgentApplication(microsoftTasksClientId, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
console.log(token)
})
let user = this.userAgentApplication.getUser()
if (!user) {
const self = this
// this.userAgentApplication = new Msal.UserAgentApplication(microsoftTasksClientId)
this.userAgentApplication.loginPopup([`${this.apiRootUrl}Tasks.readwrite`]).then(function (token) {
self.idToken = token
user = self.userAgentApplication.getUser()
if (user) {
self.getSilentToken()
}
}, function (error) {
console.log(error)
})
} else {
this.getSilentToken()
}
And my getSilentToken function
const self = this
this.userAgentApplication.acquireTokenSilent([`${this.apiRootUrl}Tasks.readwrite`]).then(function (token) {
console.log('ATS promise resolved', token)
self.accessToken = token
self.getTasks()
}, function (err) {
console.log(err)
})
Please not that my code isn't refactored AT ALL! ;-)
What version of MSAL are you using?
There is a problem in 0.1.1 version that storage is 'sessionStorage' by default and can't be really changed. In that case your login is saved just for currently opened window and you will be forced to relogin even when opened new browser window.
You should use 'localStorage' to achieve what you want and pass it as a constructor parameter for UserAgentApplication.
Here is a fix in their repo for this:
https://github.com/AzureAD/microsoft-authentication-library-for-js/commit/eba99927ce6c6d24943db90dfebc62b948355f19

Categories