Use roles to observe results or perform actions from different user perspectives. Also, it's a solution for
the forms authentication problem. Role initialization will be executed once per task on first demand and can be shared among tests and fixtures. Technically role saves created cookies and storages state. When we switch role in tests and if it's not initialized yet then initializations steps run and we return back to the page on which we stopped execution. If it's already initialized then page will be just reloaded with the new credentials.
_helpers.js_
import { Role } from 'testcafe';
export var registeredUser = Role(t => {
await t
.navigateTo('http://example.org')
.typeText('#login', 'TonyStark')
.typeText('#password', 'swordFish');
.click('#login');
});;
_test.js_
import { registeredUser } from '../helpers';
import { Role } from 'testcafe';
fixture `example.org tests`;
test('Anonymous users can see newly created comments', async t => {
await t
.switchRole(registeredUser)
.navigateTo('http://example.org')
.typeText('#comment', 'Hey ya!')
.click('#submit')
.switchRole(Role.anonymous());
var comment = await t.select('#comment-data');
expect(comment.innerText).eql('Hey ya!');
});
useRoleuseRole algorithm:
testRun in inRoleInitializer phase then throw error and abort these steps;bookmark be result of createBookmark routine.testRun.currentRoleId is not null then save previous role extended state snapshot with testRun.currentRoleId as a key;role.id then use it, otherwise run testRun.getStateSnapshotFromRole routine and use obtained snapshot.testRun.currentRoleId be role.id.bookmark.restore routine.createBookmark routine
bookmark.dialogHandler be testRun.activeDialogHandler, bookmark.iframeSelector be testRun.activeIframeSelector, bookmark.speed be testRun.speed.bookmark.ctx be testRun.ctxbookmark.fixtureCtx be testRun.fixtureCtx;testRun.activeIframeSelector is not null then execute SwitchToMainWindowCommandbookmark.urlbookmarkbookmark.restore routine
prevPhase be testRun.phasetestRun to inBookmarkRestore phase. bookmark.dialogHandler is not equal to testRun.activeDialogHandler execute SetNativeDialogHandler command with bookmark.dialogHandler value.bookmark.speed is not equal to testRun.speed then execute SetTestSpeed command with bookmark.speed value.bookmark.url.testRun.ctx be bookmark.ctx and testRun.fixtureCtx be bookmark.fixtureCtx.bookmark.iframeSelector is not equal to testRun.activeIframeSelector then execute SwitchToMainWindow command if bookmark.iframeSelector is null or SwitchToIframe command with bookmark.dialogHandler value otherwise.testRun to prevPhase testRun.switchToCleanRun routine
testRun.ctx and testRun.fixtureCtx be empty objects.null state snapshot for testRun;testRun.activeDialogHandler is not null then execute SetNativeDialogHandler command with null value.testRun.speed is not equal to testRuns.opts.speed then execute SetTestSpeed command with testRuns.opts.speed value.testRun.getStateSnapshotFromRole routine
prevPhase be testRun.phase.testRun to inRoleInitializer phase;role.phase is uninitialized then run role.initialize routine, otherwise if role.phase is pendingInitialization then wait for phase to change to initialized. role.initErr is not null then throw it and abort these steps;testRun to prevPhase ;role.stateSnapshotrole.initialize routine
role.phase to pendingInitialization;testRun.switchToCleanRun routine.role.loginPage;role.initFn; role.initErr, otherwise get current testRun state snapshot and assign it to role.stateSnapshot;role.phase to initialized;test.useRolefixture.useRoleHi Ivan,
I see you're making progress with this case. Is it already in such a state that I could test with it? Or are the bits still to be implemented vital for being able to use it?
@p-bakker
I've only made some refactorings to the moment to prepare construction site for the feature. I've decided to outline things before diving into actual implementation, because there are lots of things to consider for this feature. However, I believe we'll be able to ship it in alpha build somewhere in the end of the next week.
Things that left to consider:
Role.anonymous()? I still struggle to believe that Role() (constructor without parameters) is a comprehensive API for that. loginPage role constructor parameter should be mandatory? What about scenario when you use server code to create new user and only afterwards navigate to login page? What about t.ctx: it'll be shared between test and role or not?
@helen-dikareva good point
If we run test step-by-step we will continue debugging in role? And after?
Or if debug() was set in role, will we continue debugging test?
@helen-dikareva We should keep debugging even in role initializer
Great to see this case being closed. When is the next alpha expected?
@p-bakker We plan to release it today. I'll add note here once it will be available.
Excellent. Am already modifying my tests to start using this as soon as it's released.
While at that, I realized a function to get the active role would be helpful. Is that already included/possible?
Hello, @p-bakker! I'm happy to let you know that [email protected] is released, and now you can try the Role feature! Install it with npm install testcafe@alpha. You can get the Role API reference using this link: User Roles. As you can see, unfortunately currently there is no function to get the active role.
@p-bakker
While at that, I realized a function to get the active role would be helpful. Is that already included/possible?
We haven't considered such scenario. What's the use case for this?
@inikulin to answer your question above: in my domain specific classes that wrap all interaction with the browser, I expose an API for the writers of tests to use when they want to clean up server-side sessions, since every role equals a login equals a serverside session in my case. When they call my API to cleanup/exit a serverside session, they can pass in a Role. If that Role isn't the currently active Role, I must switch to the Role they want to exit first, before performing an exit in the app, after which I need to some back to my previously active Role.
If the role they want to exit is the active Role, I need to do different stuff.
Makes sense?
I'm playing around with this new functionality, but I'm afraid it ain't working for me.
The stuff I'm testing is a webapp with very dynamic URL, which are closely tied to serverside state.
An example of the flow:
I read the following in the new docs:
If you switch to a role for the first time in test run, the browser will be navigated from the original page to a login page where the role initialization code will be executed. Then the original page will be reloaded with new credentials. If you switch to a role that has already been initialized, TestCafe simply reloads the current page with the appropriate credentials.
The flow described here for when you switch to a role for the first time will not work in my scenario, neither will the part when you switch to an already initialized role: the reload of the current url with the credentials of another Role won't fly, because the url's are session (thus Role) specific.
So it seems to me that this new feature will not work in my scenario where url's are session/role specific and where the login procedure isn't isolated under it's own url and affects the main page under testing only by just setting a cookie of some sort.
Or am I missing something here?
To clarify: the first thing I hoped this feature would allow me to do is just log in once per fixture and have my entire testsuite broken up in individual tests (up to now I just had one test which started with the login in order to make this work). The second thing would be being able to switch between different logged in users during tests
@p-bakker So, if I understood you correct you just basically have URL-based session management? No cookies involved?
No, the session is managed by a JSESSIONID cookie, but the urls of the app are also very dynamic/unique by session
Interesting, so everything will work fine if we'll store URL in Role to which login action led and then on Role switch we redirect to this URL?
Yes, thinking the entire flow through, as far as I can see that would indeed would make it work
@p-bakker OK, it seems easy to implement. I'm only struggling to come up with meaningful option name for such behavior. Any suggestions?
Pfff, good question. Something like dynamicSessionUrls?
It's a bit difficult, because such setting would do multiple things:
Come to think of it, maybe the setting should be called something like persistUrl or useActiveUrl/useLastUrl, with a behavior that when switching Role, you get redirected back to the Url where that Role was last. In case it's the first time switching to the Role, the last url is the url where the test was when the login action led to.
Think that such behavior is more generic and could even be useful when you don't have dynamic, session-based url's, as it allows writing tests for apps/pages where you can't just go to any Url, but where there is a sort of sequence to things.
Hope that sort of makes sense
@p-bakker Unfortunately functionality that allows to preserve URL will not make it into next release - we are closing sprint today. However, I've issued a separate ticket for it and more likely we'll implement it in the beginning of the next sprint: https://github.com/DevExpress/testcafe/issues/1339. Meanwhile, you can use following workaround:
import { t, Role, ClientFunction } from 'testcafe';
const getLocation = ClientFunction(() => location.href);
const someRole = Role('http://page', async () => {
// init steps...
someRole.preservedUrl = await getLocation();
});
async function switchToRoleAndKeepUrl(role) {
await t.useRole(role);
if (role.preservedUrl)
await t.navigateTo(role.preservedUrl);
}
fixture `Fixture`;
test('Test', async () => {
//...
await switchToRoleAndKeepUrl(someRole);
//...
});
@inikulin no problem, however: I tried your workaround, but for me the code after await t.useRole(role); in switchToRoleAndKeepUrl is never executed, which means the workaround doesn't function.
Does this workaround work for you?
My code is like this:
console.log('activating session')
await TestController.useRole(session)
console.log('session acivated ', session.preservedUrl)
I see the first log statement appearing in the console, but never the second
@p-bakker I can't reproduce it on my side, this works fine for me:
import { t, Role, ClientFunction } from 'testcafe';
const getLocation = ClientFunction(() => location.href);
const someRole = Role('http://google.com', async () => {
someRole.preservedUrl = await getLocation();
});
async function switchToRoleAndKeepUrl (role) {
await t.useRole(role);
console.log('session acivated', role.preservedUrl);
if (role.preservedUrl)
await t.navigateTo(role.preservedUrl);
}
fixture `Fixture`
.page `https://devexpress.github.io/testcafe/example/`;
test('Test', async () => {
await switchToRoleAndKeepUrl(someRole);
});
@p-bakker Did you manage to make it work on your side?
@inikulin Yes, I did get it working according to how it should work.
The entire feature however doesn't not work for my specific scenario, but that is a different story, see: https://github.com/DevExpress/testcafe/issues/1339#issuecomment-289529144
This thread has been automatically locked since it is closed and there has not been any recent activity. Please open a new issue for related bugs or feature requests. We recommend you ask TestCafe API, usage and configuration inquiries on StackOverflow.