Testcafe role problems with logout - authentication

I use the Testcafe roles in my script to avoid new logins each test inside a fixture. It works really good, but I run into a problem where I cannot find any solution.
If I use the role to log in into the page and I use the common page functions to logout of the page, it means to click the selector Logout, then the upcoming script still thinks that the role is still logged in and the test fails.
As sample:
test('Login und Logout', async t => {
const loginheader = Selector('.login-header')
await t
.useRole(bc3Tester)
.navigateTo(inputStore.metaUrl)
.click(Selector('.cl-sidemenu-button'))
.click(Selector('.side-menu-logout.cl-asset-button-animation'))
await t
.wait(2000)
.expect(loginheader.exists).ok()
});
If I run this test a second time it will fail at
.click(Selector('.cl-sidemenu-button'))
because the role script not work (No auto login again)
const bc3Tester = Role(inputStore.metaUrl, async t => {
await t
.typeText(Selector('#email'), inputStore.clLogin, {
caretPos: 0
})
.typeText(Selector('#password'), inputStore.clPassword, {
caretPos: 0
})
.click(Selector('span').withText('Login'))
});
Because it will not log in. Any idea?

Related

Cypress-Cucumber-Preprocessor: Using BeforeAll for specific tags/feature files

According to the Cypress Cucumber Preprocessor docs regarding Before and After hooks:
The cypress-cucumber-preprocessor supports both Mocha's before/beforeEach/after/afterEach hooks and Cucumber's Before and After hooks.
However for some reason it doesn't seem to support the Cucumber's BeforeAll and AfterAll hooks. This is somewhat problematic for me. I'm currently trying to write some API tests that need to use an auth token that can only be obtained by manually logging in to the site first.
Ideally I would like my tests to log in through the UI only once, grab the auth token, and then run all of my API tests using that auth token.
I have all of these API scenarios tagged with #api and would love to be able to use a BeforeAll({ tags: '#api' }, () => { function (or equivalent) to have my Cypress tests log in and grab the auth token for use in those scenarios. However it seems like my only options are:
Use Before instead of BeforeAll (which would force me to login through the UI for every single scenario with the #api tag even though should I only need to do it once)
Use a Background on the feature file (which has the same problem)
Use Mocha's before hook instead of Cucumber's (which unfortunately doesn't support tagging and therefore would run before every feature file, instead of just the ones I have tagged)
Is there no way to replicate the Cucumber BeforeAll functionality with Cypress-Cucumber-Preprocessor?
The way I would approach the problem is to flag the first login in the run, and prevent the login code from running once the flag is set.
let loggedIn = false;
Before(() => {
const tags = window.testState.pickle.tags.map(tag => tag.name)
if (tags.includes('#api') && !loggedIn) {
loggedIn = true
console.log('logging in') // check this is called once
// do the login
}
})
You should also be able to get the same effect by wrapping the login code in a cy.session(), which is a cache that only runs it's callback once per run.
Before(() => {
const tags = window.testState.pickle.tags.map(tag => tag.name)
if (tags.includes('#api')) {
cy.session('login', () => {
console.log('logging in') // check this is called once
// do the login
})
}
})
Update from #rmoreltandem
This syntax is simpler
let loggedIn = false;
Before({ tags: '#api' }, () => {
if (!loggedIn) {
loggedIn = true
console.log('logging in') // check this is called once
// do the login
}
})
with session
Before({ tags: '#api' }, () => {
cy.session('login', () => {
console.log('logging in') // check this is called once
// do the login
})
})

How can I log into my web app, then read through the records of my data.json file using TestCafe

I've googled and I can find how to loop through my data file. Apparently you run a test for each record of data.
I would like to have my single test log in, then cycle through each 'record' or item of the data file. The data is a series of searches in our app. So, the test would login and assert logged in then run those searches...
test('searches', async t => {
await t
// Log in...
.typeText('input[id="login-name"]', 'aguy')
.typeText('input[id="login-password"]', 'bbb')
.click('button[id="signin-button"')
.expect(Selector('span[id="logged-in-user"]').innerText).contains('Hal')
// At this point the app is ready to run through the searches doing this...
// forEach item in my data...
.typeText('input[id="simplecriteria"]', data.criteria)
.click('button[class="search-button"]')
.expect(Selector('div[class="mat-paginator-range-label"]').innerText).contains(data.srchResult)
});
TestCafe has test hooks, I recommend using them even though they are not that usuful in your case because TestCafe deletes cookies between tests, so if you log in once and then write your test like so:
const testData = require('../Resources/testData.json');
let executed = false;
fixture `Searches`
.page(baseUrl)
.beforeEach(async t => {
if (!executed) {
// run this only once before all tests
executed = true;
// log in
await t
.typeText('input[id="login-name"]', 'aguy')
.typeText('input[id="login-password"]', 'bbb')
.click('button[id="signin-button"')
.expect(Selector('span[id="logged-in-user"]').innerText).contains('Hal');
}
});
testData.forEach((data) => {
test('Searches', async t => {
await t
.typeText('input[id="simplecriteria"]', data.criteria)
.click('button[class="search-button"]')
.expect(Selector('div[class="mat-paginator-range-label"]').innerText).contains(data.srchResult);
});
});
then you'll most likely be logged out after the first test.
However, I'd still use beforeEach hook, but put the loop inside the test:
const testData = require('../Resources/testData.json');
fixture `Searches`
.page(baseUrl)
.beforeEach(async t => {
await t
// Log in...
.typeText('input[id="login-name"]', 'aguy')
.typeText('input[id="login-password"]', 'bbb')
.click('button[id="signin-button"')
.expect(Selector('span[id="logged-in-user"]').innerText).contains('Hal');
});
test('Searches', async t => {
testData.forEach((data) => {
await t
.typeText('input[id="simplecriteria"]', data.criteria)
.click('button[class="search-button"]')
.expect(Selector('div[class="mat-paginator-range-label"]').innerText).contains(data.srchResult);
});
});
There's obvious disadvantag:
many different searches are added as one test, so if one fails, the whole "searches" test case will be marked as failed
Another solution might be to find out what it means to be logged in. If it's about adding some cookie, you might log in once and then only set up the cookie before your tests. However, in many modern systems, such "log-in cookies" will have httpOnly flag, so you can't really set it in JavaScript.

testcafe - CAS authentication

New to TestCafe. Got some simple example tests working easily. However, I wasn't able to find any examples of features that would seem to allow me to log in to my application via a CAS authentication page.
This code works to find the login button on the login page and click it.
fixture`VRT`
.page `http://myapp.com/login/`;
test('Find Login button', async t => {
const input = Selector('input.btn');
await t.click(input);
});
And this would work to type in the username and password on the login page:
test('Login using CAS', async t => {
await t
.expect("#username").exists
.typeText('#username', 'myuser')
.typeText('#password', '***')
.click('#submit');
});
But the problem is that there seems to be no way to continue from one test to another. So I can't go from the first test that opens the login page, to the next test that enters the credentials. It seems like, for TestCafe, every test has to specify its own page.
If I try to go to the CAS login page directly, by specifying it as the fixture "page", TestCafe fails to open the page, I think because the URL is extremely long.
Thanks for any suggestions.
Update:
So, using roles got me a bit further (thanks) but had to get through one more CAS page with an input button to click before getting to the page I wanted to test. Was able to add in another click to the role login:
import { Role } from 'testcafe';
import { Selector } from 'testcafe';
const finalLoginBtn = Selector('input.btn');
const myUserRole = Role('http://example.com/login', async t => {
await t
.click('input.btn')
.typeText('#username', 'my-username')
.typeText('#password', '*****')
.click('#submit')
.click(finalLoginBtn);
}, { preserveUrl: true });
fixture`VRT`
test("My User Page", async t => {
await t.navigateTo(`http://example.com`)
await t.useRole(myUserRole);
});
The TestCafe 'User Roles' functionality should suit your requirements. Please, refer to the following topic in the TestCafe documentation for details: https://devexpress.github.io/testcafe/documentation/test-api/authentication/user-roles.html

How to define a Role without login page in TestCafe?

I'm having trouble to make my tests stable with this code.
Can someone give me some directions
My app doesn't have a login page, and users might get logged in different ways, what determines if they're clogged or not is an auth cookie.
So I decided to define a role by a direct API call to the authentication API then save a cookie with the user token:
export const adminUser = Role('any-page', async t => {
const loginRequest = await fetch(
`https://my-auhtentication-api/oauth/token?grant_type=password&username=${userName}&password=${password}`,
{
method: 'POST',
}
)
const loginToken = await loginRequest.json()
await ClientFunction(() => {
document.cookie = `myAuthCookie=${loginToken.access_token}; Domain=.my-app-domain.com; Path=/`
})
})
And then I use in my code test like this:
fixture('[Admin User] Menu navigation')
.beforeEach(async t => {
await t
.useRole(adminUser)
.navigateTo(strictPage)
})
The problem is that it works fine when I run only this test, but when I run it with the rest of my stack it becomes very unstable, sometimes works, sometimes not.
For end-to-end tests, it's better if your test environment is the same as your production environment. Your comment indicates that you use a parent web app to authenticate your users in the production environment, so the best way to test authentication is using the same parent app in your test environment.

How to run multiple tests under same fixture with same browser session

I am trying to test multiple features in one test.js file with each feature implemented as a test. All these tests can be run only after login to the portal. Testcafe closes the browser after the first test which fails the subsequent tests. I used Role and .beforeEach so that the tests can log in to the portal and proceed as usual but is there any easy way to just continue all the tests in the same browser session without really closing them after each test?
I used Role feature and .beforeEach which looks like a workaround to me. Is there any other way to run all tests one after another without really closing the browser session. This will save us the login operation and the instability that might cause for each test.
import { Selector , ClientFunction, Role } from 'testcafe';
import loginpage from '../../features/blah/login/page-model'
const appPortalUser2 = Role('https://test.com', async t => {
await loginpage.signInToPortal()
await loginpage.login('test-userm', 'Password123')
}, { preserveUrl: true });
fixture `portal - settings`
.page `https://test.com/apps`
.beforeEach (async t => {
await t`enter code here`
.useRole(appPortalUser2)
});
test('settings', async t => {
//test1
await loginpage.foo1()
});
test('backup', async t => {
//test2
await loginpage.foo2()
});
Actual behavior: after test1 browser exits and login page appears which fails test2 without using .beforeEach.
Expected: browser session should continue for test2 after test1 without .beforeEach. Provide such an option where tests can continue without creating new browser sessions each time.
At the moment, there is no such option in the public API.
The idea is that one test should not affect another test in any way. If all tests had run in one browser session, every test would have influenced all preceding tests as it could have had a page with an invalid state.