Skip to content
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 · 27 comments
Closed

Implement user roles #243

inikulin opened this issue Dec 22, 2015 · 27 comments
Assignees
Labels
AREA: client AREA: server STATE: Auto-locked An issue has been automatically locked by the Lock bot. SYSTEM: API TYPE: enhancement The accepted proposal for future implementation.
Milestone

Comments

@inikulin
Copy link
Contributor

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 TYPE: enhancement The accepted proposal for future implementation. AREA: client SYSTEM: API AREA: server labels Dec 22, 2015
@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
Copy link
Contributor Author

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

@p-bakker
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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Collaborator

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

@inikulin
Copy link
Contributor Author

@helen-dikareva good point

@helen-dikareva
Copy link
Collaborator

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
Copy link
Contributor Author

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

@p-bakker
Copy link

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

@inikulin
Copy link
Contributor Author

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

@p-bakker
Copy link

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
Copy link
Contributor

AndreyBelym commented Mar 20, 2017

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.

@inikulin
Copy link
Contributor Author

@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
Copy link

@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
Copy link

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
Copy link
Contributor Author

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

@p-bakker
Copy link

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

@inikulin
Copy link
Contributor Author

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
Copy link

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

@inikulin
Copy link
Contributor Author

@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
Copy link

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
Copy link
Contributor Author

@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
Copy link

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

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

@p-bakker
Copy link

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

@lock
Copy link

lock bot commented Mar 28, 2019

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.

@lock lock bot added the STATE: Auto-locked An issue has been automatically locked by the Lock bot. label Mar 28, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Mar 28, 2019
kirovboris pushed a commit to kirovboris/testcafe-phoenix that referenced this issue Dec 18, 2019
…evExpress#1315)

* Implement Role API, refactor server tests

* Add Role id

* Rename testRun.state to testRun.phase

* Allow removal of native dialog handler. Save current iframe selector.

* Introduce marker symbol for TestRun. Store current test run speed.

* Implement test run bookmark

* useRoleMachinery implemented

* Implement UseRoleCommand

* Implement t.useRole

* Functionality implemented. Add basic test.

* Test basic functionality

* Add error tests

* Move body-parser to dev deps

* Fix tests

* Run particular tests only on desktop browsers

* Revert legacy api test-related page

* Moved error definitions to the appropriate place

* Throw appropriate message on iframe restore

* iframe errors

* Fix rebase issues
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
AREA: client AREA: server STATE: Auto-locked An issue has been automatically locked by the Lock bot. SYSTEM: API TYPE: enhancement The accepted proposal for future implementation.
Projects
None yet
Development

No branches or pull requests

6 participants