I want to test a simple end to end flow - create a new account - using Protractor.
I have an AngularJS application contains a create account page where the user needs to fill a simple form and click submit.
Clicking submit triggers a method that is calling to an async method to create the account in my server.
When that function returns, the user is directed to a different page.
Here is my test (creatAccount.spec.js):
describe('Create Account Page Tests', function() {
it('createAccount success', function(){
browser.get('http://localhost:9001/#/createAccount');
element(by.model('user.organizationName')).sendKeys('Westeros');
element(by.model('user.firstName')).sendKeys('John');
element(by.model('user.lastName')).sendKeys('Snow');
element(by.model('user.email')).sendKeys('johnSnow#westeros.com');
element(by.model('user.password')).sendKeys('123');
element(by.model('confirmPassword')).sendKeys('123');
element(by.id('submitBtn')).click();
expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/#/userManagement');
});
});
Here is my submit method:
$scope.submit = function() {
$scope.submitted = true;
UserService.createAccount($scope.user)
.then(function success(){
$state.go('userManagement');
}, function failure(){
$log.error("something went wrong");
});
};
Here is my UserService.createAccount method:
function createAccount(user){
var deferred = $q.defer();
APIService.sendRequest(APIService.ACTION().createAccount, undefined, user)
.then(function success(res) {
deferred.resolve();
}, function failure(reason){
$log.debug('create account failed. reason: ', reason);
deferred.reject(reason);
});
return deferred.promise;
}
And here is my APIService.sendRequest method:
function sendRequest(action, params, data){
var defferd = $q.defer();
var request = $resource(action.url, {}, {send: {method: action.method, isArray: action.isArray ? action.isArray : false, withCredentials: true}});
if (!params){
params = {};
}
request.send(params, data, function(res) {
defferd.resolve(res);
}, function(error) {
defferd.reject(getErrorReponseListError(error));
});
return defferd.promise;
}
My test is failing all the time since the page is not directed to the next page. It seems to me like the test does not wait for the async method to return even thought it should...
I've tried to call browser.waitForAngular() or browser.wait(5000) but nothing helps...
This line:
expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/#/userManagement');
expects the browser URL to be that value right away. It does not wait for the URL to change. The click before that line completes, but all that means is that the click was delivered to the browser, not that the handler in the browser has completed (and its async anyway).
You need to teach your Protractor test to wait for the browser to reach an expected state. Something like this should work:
browser.wait(function() {
return browser.getCurrentUrl().then(function(url) {
return url === 'http://localhost:9001/#/userManagement';
});
});
So, instead of expecting the url to be what you say, this waits for the url to be what you want. After most interactions with the browser (e.g., submitting anything or any other interaction with the server) you need to wait for the page to get to its new state.
Related
I am creating a registration form in AngularJS, wherein once the admin fills the form and on submit, I am doing two things: 1. Saving the data into the DB. 2. Sending an email to the person with his username and password. So on submitting my application is taking some time (around 5-7 seconds) to post the success message back on the screen. So I want to show some message while these background operations complete. I need your help as I am new to AngularJS.
createUserService.Register(obj, function(data) {
if (data.error) {
console.log("Check " + data.error);
} else {
alertify.alert("Successfully Registered");
$state.reload();
}
This is my service:
function Register(obj, callback) {
$http({
method : 'POST',
url : '/api/addUserOps',
data : obj
}).then(function(response) {
callback(response.data);
}, function(error) {
callback(error);
});
}
});
You can try this way.
Service function
function Register(obj) {
return $http({
method : 'POST',
url : '/api/addUserOps',
data : obj
});
}
Calling your function
// Trying using https://github.com/cgross/angular-busy for showing a loader here.
createUserService.Register(obj)
.then(function(data){
// stop your loader here with a success message.
})
.catch(function(error) {
// your error message here.
})
Hope you find this useful.
I have a non-angular entry page to my app and I'm trying to first login:
describe('Authentication', function() {
it('should authenticate a user', function() {
browser.driver.get('https://example.com')
browser.driver.findElement(by.id('username')).sendKeys("user");
browser.driver.findElement(by.id('password')).sendKeys("mypass");
browser.driver.findElement(by.tagName('input')).click()
var url = browser.getLocationAbsUrl()
browser.driver.sleep(1)
browser.waitForAngular()
return
})
})
However, this gives an error:
Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined. This could be either because this is a non-angular page or bec
ause your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"
What can I do to resolve this?
I wrote some helpers in the past to get this work in my e2e-tests:
waitForUrlToChangeTo: function (urlToMatch) {
var currentUrl;
return browser.getCurrentUrl().then(function storeCurrentUrl(url) {
currentUrl = url;
})
.then(function waitForUrlToChangeTo() {
browser.ignoreSynchronization = true;
return browser.wait(function waitForUrlToChangeTo() {
return browser.getCurrentUrl().then(function compareCurrentUrl(url) {
browser.ignoreSynchronization = false;
return url.indexOf(urlToMatch) !== -1;
});
});
}
);
},
login : function (username, password, url) {
browser.get('#/login');
element(by.model('username')).sendKeys(username);
element(by.model('password')).sendKeys(password);
element(by.buttonText('LOGIN')).click();
return this.waitForUrlToChangeTo(url);
}
And then in tests:
describe('when I login with valid credentials', function() {
it('should redirect to dashboard', function() {
helper.login('user', 'pass', '#/dashboard').then(function() {
expect(browser.getTitle()).toMatch('Dashboard');
});
});
});
I would say wait for logged in page until it displays properly and than do action. For e.g,
Target some element in logged-in page and wait for it.
Wait for url change, etc.
login -> browser.sleep(500)/wait for logged in page's element/URL change ->
other action
browser.driver.wait(function(){
expectedElement.isDisplayed().then(function (isVisible){
return isVisible === true;
},50000, 'Element not present ');
},50000);
if that element is not present within specified time, timeout error
would display & you would know unitl that time it's not logged in.
My team is working to use AngularJs and Polymer components for a new web app. I am looking into how to create a UI automation suite for this. After lots of research looks like Protractor may help me out here with some tweaks to handle Polymer. But, the current challenge is as follows -
I navigate to the app
As part of our company policy, the every web visit is validated (unless within same session). Here is how the validation works -
A login page (non-Anugular) page appears after one types the required url. Sign in with the credentials
Another intermediate page appears where it asks to wait for page to load or click a link to go to next page. Click the link
Url changes back to the original used in #1
Note: These validation pages take hell lot of time to load (changes to different internal urls). Also, the validation is skipped sometimes (within same session or through some other logic)
I have been struggling to design a prototype to handle all these. I am also trying to use Page Object while designing the prototype. Here is what I have so far.
login.js
________________________________________________________
var currentUrl;
var lastChangedUrl;
var secureUrl = 'corplogin.ssogen2.corporate.company.com';
var getwayUrl = 'gateway.zscalertwo.net';
var loginSuite = function(driver) {
var defer = protractor.promise.defer();
describe('Handle login', function() {
/*afterEach(function() {
//driver.manage().deleteAllCookies();
})*/
//it('Login to security test', function(){
//********** Wait for page to load/URL to change to secure login page ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then(function() {
//********** login to secure page ************
if (lastChangedUrl.indexOf(secureUrl) > -1 || lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.name("username"));
element.sendKeys("Username");
element = driver.findElement(By.name("password"));
element.sendKeys("password"); //Give password
element = driver.findElement(By.name("submitFrm"));
element.click();
}
}).then (function() {
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
}).then (function() {
//********** Click on the link to to go to test page ***********
if (lastChangedUrl.indexOf(getwayUrl) > -1) {
var element = driver.findElement(By.tagName("a"));
console.log("before click............");
element.click();
}
//********** page is slow. wait for page to load/URL to change ************
driver.getCurrentUrl().then(function(url) {
currentUrl = url;
}).then(function() {
driver.wait(function() {
return driver.getCurrentUrl().then(function (url) {
lastChangedUrl = url;
return url !== currentUrl;
});
});
})
.then (function() {
//return defer.promise;
//browser.pause();
});
}, 60000);
});
//});
}, 60000);
return defer.promise;
};
module.exports = loginSuite;
spec.js
___________________________________________________________________________
describe('Protractor Demo App', function() {
var myUrl = 'http://<my test app url>/';
var driver = browser.driver;
beforeEach(function() {
driver.get(myUrl);
});
it('should login', function() {
loginSuite(driver)
.then(
function(){
console.log("End of tests:");
expect(driver.getCurrentUrl()).toBe(myUrl);
});
});
The issue here -
My expectation here is to have the promise returns to spec.js after the secure login page is handled so that I can continue with other testing using the driver object. For the sake testing I am logging 'End of tests' message and doing a dummy validation. But, looks like those two lines don't get executed.
Login to the secure site works and I see page changes to original test page. I tested that with Browser.pause(). But, the logging 'End of test' never happens, nor the validation.
I need to handle the scenario where the secure login page doesn't appear. Not sure what adjustment I need to do in login.js page
Is my approach for page object and handling the promises wrong here? I am able to go to one step further on the test app page when all the code are placed under one js file instead of splitting them for page object. Please help here.
I wanted to share with you the "polymer way" of solving your problem.
The code below use two elements to monitor the URL, the auth flow, the previous page visited and log the user in/out of the app
The first will bind to the origin route, so you can send the user back there
<app-route
route="{{route}}"
pattern="/origin/:page"
data="{{data}}"
tail="{{subroute}}">
</app-route>
The second will bind to the authPage, allowing you to show/hide the auth page.
<app-route
route="{{subroute}}"
pattern=":authPage"
data="{{data}}
active="{{authPageActive}}">
</app-route>
User auth, monitoring and page redirecting
Use the element: <firebase-auth>
Is the user singned in?: signedIn="{{isSignedIn}}"
<firebase-auth id="auth" user="{{user}}" provider="google" on-
error="handleError" signedIn="{{isSignedIn}}"></firebase-auth>
Add an observer
observers: [
'_userSignedInStatus(isSignedIn)' // observe the user in/out
],
Add a Function
_userSignedInStatus: function (isSignedIn) {
if (isSignedIn === false) {
this.page = 'view404'; // redirect the user to another page
// import an element that cover the view
} else {
//send a log message to your database
}
}
I have a SocketService (using socket.io) and I make a call to my backend server like so:
SocketService.on("get user data", (response) => {
DataService.data = response;
});
I have a controller like so:
...
.controller("UserController", ($scope, DataService) => {
SocketService.emit("get user data");
$scope.users = DataService.data;
});
Now, this is the flow:
Controller loads, makes a request to our backend using SocketService.
$scope.users becomes equal to an empty array since we didn't get a response yet.
SocketService listens for the response, updates DataService.
The problem is, unless I click the button to load UserController again, I see an empty array. So, first click, I see an empty array. Second click after 1 seconds, I see the users array.
Is there any way to do something like this on Angular?
// pseudo code
SocketService.emit("get user data");
Spinner.show();
SocketService.on("get user data", (response) => {
angular.controllers.resolve("UserController", ($scope) => {
$scope.users = response;
});
Spinner.hide();
});
I don't know your SocketService with socket.io, but if it were a normal angular service and call the backend via $http i would do following:
SocketService (inject $http):
function get(url, config) {
return $http.get(url, config);
};
UserController (inject your SocketService):
function getUserData() {
SocketService.get('myUrl', {})
.then(function(response) {
$scope.users = response.data;
});
}
I hope this give you an impulse to solve your problem.
So I have been working with basic Backbone.js to create a simple demo page that is a basic User Manager. I run the same function whether I am creating (POST) or updating (PUT) a user's information. Both the POST and PUT requests are successful (I.E. calling my "success" function) however only the PUT command will trigger the router, the POST request does not trigger the same line of code while it DOES trigger the success function.
Below is my events code. The saveUser function takes care of both the PUT and POST requests but the router in success is only fired from a successful PUT request, not from a successful POST request (even though both are successful in updating the database).
events: {
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function (ev) {
var userDetails = $(ev.currentTarget).serializeObject();
var user = new User();
reply = user.save(userDetails, {
success: function (){
router.navigate('', {trigger: true});
},
error: function(){
$("#editError").toggle();
$("#editError").html("Error:<br/>"+reply.status+" : "+reply.statusText+"<hr/>");
}
});
return false;
},
deleteUser: function (ev){
$.ajaxSetup({
headers: {
'method':"DeleteUser"
}
});
reply = this.user.destroy({
success: function (){
router.navigate('', {trigger: true});
},
error: function(){
$("#editError").toggle();
$("#editError").html("Error:<br/>"+reply.status+" : "+reply.statusText+"<hr/>");
}
})
return false;
}
Here is the router code:
var Router = Backbone.Router.extend({
routes:{
'new': 'editUser',
'edit/:id': 'editUser',
'': 'home'
}
});
var router = new Router();
//ROUTES
router.on('route:home', function(){
alert("1");
userList.render(),
editUser.render({});
});
router.on('route:editUser', function(id){
userList.render();
editUser.render({id: id});
});
//NECESSARY
Backbone.history.start();`
Any help would be appreciated!
Thanks!
The problem is that when you're creating a new user, you're already at the url "", and router.navigate(newUrl) only triggers if newUrl is different than the current page url.
See https://github.com/documentcloud/backbone/issues/652.
To fix, change
router.navigate('', {trigger: true});
To
Backbone.history.fragment = null; // Forces the current URL to be dirty
router.navigate('', {trigger: true}); // Now, Backbone thinks the new URL is different
When you post, the current url is "http://gocella.com/backbone/#", after you post you navigate to "http://gocella.com/backbone/#" which doesn't change the url, which won't fire the event.
Router only triggers the event when the url changes, it works from update because when you hit edit your url changes to "http://gocella.com/backbone/#/edit/1", and then the trigger changes back to "http://gocella.com/backbone/#"
I think the approach of using the router for this problem is just overall incorrect.
You should do something more along the lines of:
when you hit save, create a new model or update the existing model with the form values, then use collection.create(modelFromForm) or, if you use model.save, then on the success callback, add it to the collection