I have a single-page app that has a Ruby on Rails backend and uses Authlogic to facilitate user authentication.
I am implementing functional testing using CasperJS and am having a hard time getting login sessions to persist between sessions but also between thenOpen commands.
I am running the following command:
casperjs --cookies-file=cookies.txt test ../../../foobar/test/casper/login/test.js
Here's an example of my current code:
phantom.cookiesEnabled = true;
var x = require('casper').selectXPath
casper.test.begin('Logging in', 2, function suite(test) {
casper.start('http://localhost:3000/login', function() {
console.log("Page loaded");
test.assertExists('form', 'login form is found');
this.fillSelectors('form', {
'#email': "foo#bar.com",
'#password': "foo_bar"
}, false)
this.click('#submit')
casper.thenOpen('http://localhost:3000/my', function() {
test.assertUrlMatch(this.getCurrentUrl(), 'http://localhost:3000/my', "Logged in and maintained login cookie")
})
})
casper.run(function() {
test.done();
});
})
While watching my dev log, I can see that the first test (casper.start) logs in successfully but after the thenOpen, the Authlogic UserSession is no longer maintained so PhantomJS gets redirected to localhost:3000/login, which is what should happen if there is no logged-in user.
How can I maintain a logged-in session using CasperJS between thenOpen but also between multiple test runs? Can I maintain cookies so that the user remains logged-in between tests?
Seems like this comment saved the day
Looking at the code you have this.click and then doing a
casper.thenOpen I would add another step after the form submission and
use a casper.waitForXXXX.
It does look like a timing issue
Related
I am developing tests for a website that requires login for the whole page. This website uses Google Sign-In for authentication. All of the Google accounts used for authentication require two-factor authentication.
This means to test one part of the website, the test suite needs to sign in for each test three times - which eventually leads to problems with authentication requiring human interaction, or even requiring extra steps to authenticate.
To resolve this problem, I followed the Reuse Signed-In State part of the Playwright documentation so that sign in would happen once and then saved and shared among the tests.
This works fine for just normal tests. However, I also started using Page Object Models to describe my pages and allow for easier maintenance of the test suite. For some reason, in tests that create new instances of Page Object Model classes, the tests do not make use of the saved state and thus are not logged in.
How can this saved state be passed onto a POM instance in Playwright so that signed-in state can be reused? Perhaps I've missed something simple?
Used a global-setup.ts file to declare login:
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto("http://localhost:8000/login");
await page.getByRole("button", { name: "Connexion" }).click();
await page.getByRole("button", { name: "Continue with Google" }).click();
await page.getByRole("textbox", { name: "Adresse e-mail ou numéro de téléphone" }).fill(process.env.USERNAME);
await page.getByRole("textbox", { name: "Adresse e-mail ou numéro de téléphone" }).press("Enter");
await page.getByRole("textbox", { name: "Saisissez votre mot de passe" }).fill(process.env.PASSWORD);
await page.locator("#passwordNext").click();
await page.goto("http://localhost:8000");
// Save signed-in state to 'storageState.json'.
await page.context().storageState({ path: "storageState.json" });
await browser.close();
}
I also added this to the playwright.config.ts file:
const config: PlaywrightTestConfig = {
globalSetup: require.resolve('./global-setup'),
use: {
storageState: 'storageState.json'
}
};
Here is an example of a test making use of the POM, which then doesn't use the state created in global setup. The login still happens, but then the new browser opened doesn't have the state and thus gets stuck on the login page:
test("apply a filter", async ({ page }) => {
const dashboardHome = new DashboardHome(page);
await new LoginPage(page).login();
await dashboardHome.bookingLink.waitFor();
await dashboardHome.bookingLink.click();
const reservationsPage = new ReservationsPage(page);
await reservationsPage.orderNumberFilter.waitFor();
reservationsPage.filter({ orderID: "22222" });
await expect(page).toHaveURL("http://localhost:8000/booking/22222");
});
I had similar issues with global setup and re-write my global setup in fixture (kinda global) where I used it everywhere. I amn't very experienced in test automation and can't explain why globalsetup didn't work. I also believe it's something simple, that i can't catch. Hope it help you.
That definitely seems confusing, especially since switching to a POM shouldn’t affect it (I have successfully used the POM and this one-time saved and reused auth).
I can’t think how switching to use page objects would affect it, and would be curious to see your setup. But one potential reason for your problem is that I’ve seen and worked with sites where if you go to the login page, you’re automatically “logged out” in some way, thus negating your login during setup. If you already logged in during setup and saved that auth state, you should be able to bypass/skip the login page altogether in the test and go directly to the page you need to work with. I would actually expect/be curious if you run into the same problem when not using the POM.
Let me know if that solution works for you, or if you even still have the issue at this point, but if that’s not the issue I may need more clarification or info to help further.
I'm running into an issue which I can't determine the cause off. I have defined the following commands within Cypress:
Cypress.Commands.add('createUser', (opts, permissions = []) => {
railsRequest('create_user', { user: opts, permissions })
.its('body')
.its('response');
});
Cypress.Commands.add('create', (type, opts) => {
railsRequest('create', { type, opts })
.its('body')
.its('response');
});
Cypress.Commands.add('login', { prevSubject: true }, (subject) => {
railsRequest('login', { id: subject.id })
.its('body')
.its('response')
.as('currentUser');
});
The createUser command is fairly self explanatory, the create command allows me to create dummy data on the server and the login command logs the previously created user in to the server setting the session cookie.
I've got the following spec (which currently has no assertions as I'm playing around) which I'm having issues with:
describe('My Spec', () => {
beforeEach(() => {
cy.createUser({}, ['permission_name'])
.login();
});
it('testing', function() {
cy.visit('/');
cy.create('other_resource', { name: 'resource name' }, company_id: this.currentUser.company_id)
.as('resource')
.then(resource => {
cy.visit('/resource');
});
});
});
Nothing particularly complicated, but here is what's going on:
User is created
Login takes place
visits '/' which works properly. Chrome shows the page loading as expected
New 'resource' is created
When ready, visits '/resources' - here however the app redirects to the login page. Seemingly the user has been logged out.
What I don't get is why the user is being logged out. I added in the cookie debugging and see the session cookie being changed on each request so it seems like the session cookie is OK, but something clearly isn't working correctly.
We have no issues with the app itself when running so I don't THINK it's the back end but if someone has any idea of what's going on I'd love some insight
Ok so it was me being an idiot in the cypress controller in rails. I've got a controller responsible for the calls to create data and handle any ruby code, but on the create call it was setting up the test suite for the helpers that it uses, but the method that was responsible for that was also clearing the database. So essentially, the user was being logged in, then when it asked the server to create the dummy data it wiped the database (including the new user) and then tried to continued.
Needless to say any future request resulted in being redirected to the login page because the user didn't exist
I have an Ember CLI app with a Rails back-end API. I am trying to set up end-to-end testing by configuring the Ember app test suite to send requests to a copy of the Rails API. My tests are working, but I am getting the following strange error frequently:
{}
Expected: true
Result: false
at http://localhost:7357/assets/test-support.js:4519:13
at exports.default._emberTestingAdaptersAdapter.default.extend.exception (http://localhost:7357/assets/vendor.js:52144:7)
at onerrorDefault (http://localhost:7357/assets/vendor.js:42846:24)
at Object.exports.default.trigger (http://localhost:7357/assets/vendor.js:67064:11)
at Promise._onerror (http://localhost:7357/assets/vendor.js:68030:22)
at publishRejection (http://localhost:7357/assets/vendor.js:66337:15)
This seems to occur whenever a request is made to the server. An example test script which would recreate this is below. This is a simple test which checks that if a user clicks a 'login' button without entering any email/password information they are not logged in. The test passes, but additionally I get the above error before the test passes. I think this is something to do with connecting to the Rails server, but have no idea how to investigate or fix it - I'd be very grateful for any help.
Many thanks.
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'mercury-ember/tests/helpers/start-app';
module('Acceptance | login test', {
beforeEach: function() {
this.application = startApp();
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('Initial Login Test', function(assert)
{
visit('/');
andThen(function()
{
// Leaving identification and password fields blank
click(".btn.login-submit");
andThen(function()
{
equal(currentSession().get('user_email'), null, "User fails to login when identification and password fields left blank");
});
});
});
You can check in the Network panel of Chrome or Firefox developer tools that the request is being made. At least with ember-qunit you can do this by getting ember-cli to run the tests within the browser rather than with Phantom.js/command-line.
That would help you figure out if it's hitting the Rails server at all (the URL could be incorrect or using the wrong port number?)
You may also want to see if there is code that needs to be torn down. Remember that in a test environment the same browser instance is used so all objects need to be torn down; timeouts/intervals need to be stopped; events need to be unbound, etc.
We had that issue a few times where in production there is no error with a utility that sent AJAX requests every 30 seconds, but in testing it was a problem because it bound itself to the window (outside of the iframe) so it kept making requests even after the tests were torn down.
So I've got an question about authentication and have been wondering how other people might handle this situation. I'm currently running an Angular app that is built on a Rails API.
So far for authentication I have a form that does a post to the Rails side which logs the user in and then sends them back to the Angular app on success. Once the cookie is set and the user is logged in, I'm able to access a user.json file which contains all the User information one might expect (Id, username, roles, rights, etc). Since verification all happens on Rails, if the user logs out then this information is removed. So the two states look like so...
Logged in
{
id: 99384,
name: "Username",
url: "//www.test.com/profiles/Username",
timezone: null,
rights: [ ],
roles: [
"admin"
],
}
Logged out
{
error: "You need to login or join before continuing."
}
So far I've seen all these millions of different ways to do auth for Angular, but it seems like nothing fits this type of method. So my question is, since the server is handling all of the verification, is there a way to just check if they user.json file is empty (displaying the error message) and if it is send the Angular app to the Rails login page? Is there really any point messing with Cookies, Tokens, etc when I can base it all on the JSON file?
You are already using cookies - the server is setting them. What you have done is a fairly standard way of doing things.
To check the json file, you can do something like this stub shows in your controller:
app.controller('AppControl', function($scope, $http, $location){
// Get the JSON file.
$http.get('/path/to/json/file')
.then(response){
if(response.data.error){
// redirect to login
$location.path('login');
}
else{
$scope.user = response.data;
// your app code here.
}
})
.catch(function (error){
// unable to reach the json file - handle this.
});
});
Of course, you should really move this out into a service so you can re-use it, and also cache the data, rather than getting the user every time you change route/page, but this gives you a vague idea.
EDIT Example factory:
.factory('User', function( $http ){
// Create a user object - this is ultimately what the factory will return.
// it's a singleton, so there will only ever by one instance of it.
var user = {};
// NOTE: I am assigning the "then" function of the login promise to
// "whenLoggedIn" - your controller code is then very easy to read.
user.whenLoggedIn = $http.get('user.json')
.then(function(response){
// Check to see if there is an error.
if (response.data.error !== undefined) {
// You could be more thorough with this check to determine the
// correct action (examine the error)
user.loggedIn = false;
}
else {
// the user is logged in
user.loggedIn = true;
user.details = response.data;
return user;
}
}).then; // <-- make sure you understand why that .then is there.
return user;
})
Usage in the controller
.controller('ExampleController', function($scope, User){
// It's handy to have the user on the scope - you can use it in your markup
// like I have with ng-show on index.html.
$scope.User = User;
// Do stuff only if the user is loggedin.
// See how neat this is because of the use of the .then function
User.whenLoggedIn( function (user){
console.log(user.details.name + " is logged in");
});
});
Because it's on the scope, we can do this in the html:
<body ng-controller="ExampleController">
<h1 ng-show="User.loggedIn == null">Logging in..</h1>
<h1 ng-show="User.loggedIn == true">Logged in as {{ User.details.name }}</h1>
<h1 ng-show="User.loggedIn == false">Not logged in</h1>
</body>
Here is an example on plunker where this is working.
Note the following:
If the user is/was already logged in, when you inject the service in the future, it won't check the file again. You could create other methods on the service that would re-check the file, and also log the user out, back in, etc. I will leave that up to you.
There are other ways to do this - this is just one possible option!
This might be obvious, but it's always worth saying. You need to primarily handle authentication and security on the server side. The client side is just user experience, and makes sure the user doesn't see confusing or conflicting screens.
I'm using Grails 2.1.1 with Cucumber and Geb. I have an Auth.feature that contains 2 scenarios. One is for authenticating successfully and the other is for testing invalid credentials.
The way I think I have to work this out is to have geb log out the user from the first scenario before it can run the second scenario. This is because my Given step checks to make sure I'm at the login page. After scenario 1 executes, I'm on a Dashboard page.
I guess my question is do I (a) use geb to sign out the valid user before completing the scenario or (b) is there a way to have it start over between scenarios?
Right now, I've implemented (a) and it works just fine. Just want to know if this is optimal.
Here is my feature
Feature: login to system
As a user of the system
I want to log in to the application
so that I can use it
Scenario: login
Given I access the login page
When I enter valid credentials
Then I see the dashboard
Scenario: auth fail
Given I access the login page
When I enter invalid credentials
Then I see appropriate error messages
And here is my Geb steps
Given(~'^I access the login page$') {->
to LoginPage
at LoginPage
}
When(~'^I enter valid credentials$') {
page.add('user_10001#test.com', '10001')
}
Then(~'^I see the dashboard$') {->
at DashboardPage
}
Then(~'^I see an error message on the login page$') { ->
at LoginPage
}
When(~'^I enter invalid credentials$') { ->
page.add('baduser', 'paddpassword')
}
Then(~'^I see appropriate error messages$') { ->
at LoginPage
// check for error message
}
Based on some more research I've done, it looks like there are a few ways to handle this:
Just like I am already doing it, by logging out at the end of a scenario (or you could do it at the beginning
Make logging out its own scenario
In the env.groovy Before hook, add to LogoutPage
Logout using a Background
Add the following line to the After hook in env.groovy:
bindingUpdater.browser.clearCookies()