New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement user roles #243

Closed
inikulin opened this Issue Dec 22, 2015 · 26 comments

Comments

@inikulin
Copy link
Collaborator

inikulin commented Dec 22, 2015

Roles

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!');
});

@inikulin inikulin added this to the APIv2 milestone Dec 22, 2015

@inikulin inikulin modified the milestones: APIv2 new goodies, APIv2 MVP Feb 26, 2016

@inikulin inikulin changed the title APIv2: Implement roles Implement user roles Sep 9, 2016

@inikulin inikulin modified the milestones: Sprint #5, Planned features Feb 14, 2017

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Feb 14, 2017

  • Add cookie jar switcher on hammerhead side
  • Implement Role constructor
  • Implement useRole

useRole algorithm:

  1. If testRun in inRoleInitializer phase then throw error and abort these steps;
  2. Let bookmark be result of createBookmark routine.
  3. If testRun.currentRoleId is not null then save previous role extended state snapshot with testRun.currentRoleId as a key;
  4. If there is saved extended state snapshot for the role.id then use it, otherwise run testRun.getStateSnapshotFromRole routine and use obtained snapshot.
  5. Let testRun.currentRoleId be role.id.
  6. Call bookmark.restore routine.

createBookmark routine

  1. Let
    • bookmark.dialogHandler be testRun.activeDialogHandler,
    • bookmark.iframeSelector be testRun.activeIframeSelector,
    • bookmark.speed be testRun.speed.
    • bookmark.ctx be testRun.ctx
    • bookmark.fixtureCtx be testRun.fixtureCtx;
  2. If testRun.activeIframeSelector is not null then execute SwitchToMainWindowCommand
  3. Get current page URL and store it as bookmark.url
  4. Return bookmark

bookmark.restore routine

  1. Let prevPhase be testRun.phase
  2. Switch testRun to inBookmarkRestore phase.
  3. If bookmark.dialogHandler is not equal to testRun.activeDialogHandler execute SetNativeDialogHandler command with bookmark.dialogHandler value.
  4. If bookmark.speed is not equal to testRun.speed then execute SetTestSpeed command with bookmark.speed value.
  5. Navigate to bookmark.url.
  6. Let testRun.ctx be bookmark.ctx and testRun.fixtureCtx be bookmark.fixtureCtx.
  7. If 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.
  8. Switch testRun to prevPhase

testRun.switchToCleanRun routine

  1. Let testRun.ctx and testRun.fixtureCtx be empty objects.
  2. Set null state snapshot for testRun;
  3. If testRun.activeDialogHandler is not null then execute SetNativeDialogHandler command with null value.
  4. If testRun.speed is not equal to testRuns.opts.speed then execute SetTestSpeed command with testRuns.opts.speed value.

testRun.getStateSnapshotFromRole routine

  1. Let prevPhase be testRun.phase.
  2. Switch testRun to inRoleInitializer phase;
  3. If role.phase is uninitialized then run role.initialize routine, otherwise if role.phase is pendingInitialization then wait for phase to change to initialized.
  4. If role.initErr is not null then throw it and abort these steps;
  5. Switch testRun to prevPhase ;
  6. Return role.stateSnapshot

role.initialize routine

  1. Set role.phase to pendingInitialization;
  2. Call testRun.switchToCleanRun routine.
  3. Navigate to role.loginPage;
  4. Execute role.initFn;
  5. If there is an execution error then save it to role.initErr, otherwise get current testRun state snapshot and assign it to role.stateSnapshot;
  6. Set role.phase to initialized;

  • test.useRole
  • fixture.useRole

inikulin added a commit to inikulin/testcafe-hammerhead that referenced this issue Mar 3, 2017

miherlosev added a commit to DevExpress/testcafe-hammerhead that referenced this issue Mar 3, 2017

@inikulin inikulin self-assigned this Mar 7, 2017

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 9, 2017

Hi 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?

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 9, 2017

@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.

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 9, 2017

Things that left to consider:

  • Do we need Role.anonymous()? I still struggle to believe that Role() (constructor without parameters) is a comprehensive API for that.
  • Is 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?
@helen-dikareva

This comment has been minimized.

Copy link
Collaborator

helen-dikareva commented Mar 13, 2017

What about t.ctx: it'll be shared between test and role or not?

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 13, 2017

@helen-dikareva good point

@helen-dikareva

This comment has been minimized.

Copy link
Collaborator

helen-dikareva commented Mar 13, 2017

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?

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 13, 2017

@helen-dikareva We should keep debugging even in role initializer

@inikulin inikulin closed this in c7af417 Mar 20, 2017

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 20, 2017

Great to see this case being closed. When is the next alpha expected?

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 20, 2017

@p-bakker We plan to release it today. I'll add note here once it will be available.

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 20, 2017

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?

@AndreyBelym

This comment has been minimized.

Copy link
Collaborator

AndreyBelym commented Mar 20, 2017

Hello, @p-bakker! I'm happy to let you know that testcafe@0.14-alpha4 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.

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 20, 2017

@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?

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 20, 2017

@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?

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 20, 2017

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 open the app by going to domain:port/myApp
  • As the server determines I'm not yet logged in, it redirects me to domain:port/somethingRandom
  • if I provide proper credentials, the server again redirects me to domain:port/anotherRandomValue

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

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 20, 2017

@p-bakker So, if I understood you correct you just basically have URL-based session management? No cookies involved?

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 20, 2017

No, the session is managed by a JSESSIONID cookie, but the urls of the app are also very dynamic/unique by session

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 21, 2017

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?

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 21, 2017

Yes, thinking the entire flow through, as far as I can see that would indeed would make it work

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 21, 2017

@p-bakker OK, it seems easy to implement. I'm only struggling to come up with meaningful option name for such behavior. Any suggestions?

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 21, 2017

Pfff, good question. Something like dynamicSessionUrls?

It's a bit difficult, because such setting would do multiple things:

  • change the behavior of of switching between Roles: currently it reloads the current URL with a different Role, so the test remains in the exact same 'route' in your webapp/webpage. The new behavior would redirect the test back to the URL where the app/page was after login everytime.

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

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 22, 2017

@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: #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);
     //...
});
@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 22, 2017

@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

@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 22, 2017

@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);
});
@inikulin

This comment has been minimized.

Copy link
Collaborator

inikulin commented Mar 26, 2017

@p-bakker Did you manage to make it work on your side?

@p-bakker

This comment has been minimized.

Copy link

p-bakker commented Mar 27, 2017

@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: #1339 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment