Angular Protractor end-2-end test fails after clicking a button - angularjs

So today I have been working on setting up end-2-end testing for an Angular JS app with Protractor. For writing cleaner tests, I make use of the Page Object pattern as described on the Protractor website.
Test scenario: When a user enters the website, he/she needs to login first. By filling in their credentials and clicking a button, the user is redirected to a validation page. There they need to enter a code that has been sent to them by email. In this case, the validation code is always the same when running end-2-end tests (123456). When the user has entered his/her code, the validation button needs to be clicked in order to enter the landing page (organisations). There a list of organisations is presented to the user.
The Protractor test:
organisations.spec.js
'use strict';
describe('when selecting organisation', function () {
var page;
var util = require('../../util');
browser.get('/#/login');
// login before each test case
beforeEach(function () {
page = require('./organisations.po');
util.login('username', 'password');
});
// logout after each one
afterEach(function () {
page.logoutButton.click();
});
it('should have a list of organisations', function () {
expect(browser.getCurrentUrl()).toEqual('http://localhost:9111/#/organisations');
expect(page.organisationList.isPresent()).toBe(true);
});
});
organisations.po.js (the page object)
'use strict';
var MainPage = function () {
// organisations
this.organisationList = element(by.css('.select-list'));
this.logoutButton = element(by.css('.logout-button'));
};
module.exports = new MainPage();
util.js (code for login process)
'use strict';
var Util = function () {
this.login = function login(username, password) {
browser.get('/#/login');
var loginPage = require('./spec/login/login.po');
var validationPage = require('./spec/login/validate.po');
// fill in login form
loginPage.username.sendKeys(username);
loginPage.password.sendKeys(password);
// submit login form and navigate to validation page
loginPage.loginButton.click();
// fill in validation form
validationPage.validationCode.sendKeys('123456');
// submit validation form and navigate to landing page
validationPage.submitValidateBtn.click();
}
};
module.exports = new Util();
What happens when running the test: Protractor is able to fill in the user credentials and click the login button. However, the corresponding redirect to the validation page does not occur, causing the expectations to fail as Protractor is not able to locate the page objects since these are on another page. Here is a part of the error message that Protractor produces. Just in case, here can you see the Protractor configuration file that is being used.
I tried putting explicit waits (browser.wait(<condition>) around the button clicks, but that just seems to have the same effect. Could someone point out what I am doing wrong and why that redirect is not happening? Am I missing something obvious here? Thanks in advance!

If your login-pages are non-Angular, double check whether you need to set browser.ignoreSynchronization to false in Protractor.
If they are Angular, I would think you'd need to wait for the click promise to complete after login? Does the following work?
loginPage.loginButton.click().then(function () {
validationPage.validationCode.sendKeys('123456');
...
}
In this way, Protractor does the wait (suggested in other answers) itself, provided the login/validation page are indeed done in Angular.
As a related remark, in your beforeEach and afterEach, you may also want to use the optional done callback, so that the actual test only starts after login has been successful:
beforeEach(function (done) {
...
util.login(user, pw).then(done);
}
This does require, though, that your login method actually returns the promise of the validation click. (I typically let all my page object methods return the promises).
Also related to your page objects, instead of having a Login page object with just the element finders as fields, why not give that page object the more abstract login method that is now in a util class?
Furthermore, I tend to give every page object a 'self check' method, that permits checking that the browser is indeed on the expected page. After clicking login on the Login page, I expect to be on the validation page. Thus, the first thing I do is check (assert) that I'm indeed on the validation page. Only then do I actually press the validate button. This tends to give cleaner error messages in case your application's routing is broken.
I wrote a bit more about state navigation and page objects on my blog.

I've had some issues with code in a part further along interrupting at a spot earlier than you'd expect. For example, as soon as it loads up a page object that has the right path, but maybe an error with the declaration of the page itself, the whole test crashes.
First, I would comment everything out after clicking the login button. Then maybe add a 10s sleep. Run the test and watch it. Does it click the button? Does it get past the login screen?
If that is working, uncomment one line at a time and see where it crashes. Try keeping the sleep in there to see if it makes a difference. If the sleep does make a difference, that means whatever wait you were using wasn't one that works in that situation.
Of course, if you hit a line that crashes you every single time, then you can dig further!

what I have found to help work is to force protractor to wait till a page loads by waiting till the title is a specific title.
for example: the title could be login at your login page but then after you hit login the next page title is validation. So you can force protractor to wait till the validation page loads.
NOTE: I don't know the actual names of your titles so I'm just using place holders.
'use strict';
var Util = function () {
this.login = function login(username, password) {
browser.get('/#/login');
var loginPage = require('./spec/login/login.po');
var validationPage = require('./spec/login/validate.po');
// fill in login form
loginPage.username.sendKeys(username);
loginPage.password.sendKeys(password);
// submit login form and navigate to validation page
loginPage.loginButton.click();
// wait till our validation page loads then do our validation
browser.wait(protractor.until.titleIs("Validation"), 5000, "✗ Failed to wait for the validation page to load").then(function(){
// fill in validation form
validationPage.validationCode.sendKeys('123456');
// submit validation form and navigate to landing page
validationPage.submitValidateBtn.click();
});
}
};

Related

AngularJS reloading multiple tab in the same time if any change

I have an add form that if the user file the form and submit, in the same time, user can see the data in the same page in table with ui-view and this is not an issue!!
Problem is: if i open more than one tab in the same browser, that post or change reflection doesn't show in other tab without refreshing or reloading. i want to see change in other tab of browser or other browser without reload
I want to see the change without refresh or reloading.
if i reload in other tab, it works but if i dont reload in other tab, the change doesn't effect
here you go for my post method:
$scope.formModel = {};
$scope.onSubmit = function () {
$http.post('http://127.0.0.1:8000/api/v1/contact/create/', $scope.formModel)
.then(function(response) { //if success, below fuction will execute
$scope.successPost = 'You have successfully submitted your Contact';
$timeout(function() {
$scope.successPost = '';
}, 4000);
//below $scope will push data in client site if the request is success
$scope.contacts.push(response.data);
//if any error occurs, below function will execute
}, function(response) {
// below variable will get predefined message that will explain exactly what happened
var errorData = response.data;
$scope.errorPost = Object.values(errorData)[0][0];
$timeout(function(){
$scope.errorPost = '';
}, 4000);
});
$scope.formModel = {}; //It means, after submit, the form field will clear
$scope.addContactForm.$setPristine();
};
I want to see the change in the other tab without refresh or reload
well browser tabs are per definition separated from each other and can't communicate directly between each other.
there are certain possibilities to handle that: for example you can have a separate external service and always poll that service for latest changes and if something changes automatically call reload.
or you could use a service that provide a websocket that sends you to the frontend an update notification once something changed in your data.
Just want to point you in the right direction, the topic is to big to provide a finished solution. I recommend to do some research on how to communicate between tabs / how to build a service with an update notification for new data
Edit: Just to add #Yftach point from the comments: Theoretically localstorage is shared between tabs, so theoretically you could have a variable there and watch in an interval for changes. However the solution with a separate server is (at least in my eyes) way better

Angular app, but protractor finds Angular in in some cases but not others

I have a Protractor test that works in Chrome, and when I added multicapabilities to the config file to cover Chrome and Firefox, several FIrefox attempts returned "Error while waiting for Protractor to sync with the page: "both angularJS testability and angular testability are undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping."
I'm also getting this message for a page in Chrome that our developers have said IS in Angular.
Another test of an Angular page works as I go through entering information to go through account creation, but when the test tries to click Terms of Service, it results in that message right after the Selenium Server starts, even with browser.waitForAngularEnabled(false); in the code. With that whole section commented out, it passes again consistently.
This is the code for that test, stripped of some other things in the attempt to debug:
describe('Profile Sign up with email', function() {
it('should load profile signup page', function() {
browser.get('https://the_url_i_need.com');
});
it('should select Sign Up', function() {
element(by.css('label.ng-isolate-scope:nth-child(4) > span:nth- child(1)')).click();
});
it ('should make sure email address is the selected form of sign up', function() {
element(by.id('Radio1')).click();
});
browser.sleep(7000);
element(by.linkText('Terms of Service')).click();
});
Now, if "element(by.linkText('Terms of Service')).click();" is commented out, the test is fine; whether or not I have "browser.waitForAngularEnabled(false);" or the lengthy sleep before it seems irrelevant. If the Terms of service link click is included, then the test returns the error above.
Since this is happening in multiple tests, how can I stabilize them in this regard?
The error occurs, because you want to execute the click on that link before you have loaded an URL, because you don't execute it in an it(...) function.
Try following please:
describe('Profile Sign up with email', function() {
...
it ('should click successfully on Terms of Service', function() {
browser.sleep(7000);
element(by.linkText('Terms of Service')).click();
});
});

making a ui-sref functionality in a .js

Setting:
I have an angular app.
What I have:
I have this: ui-sref="providerDetail({id:provider.id})" functioning.
It is a functioning link to a page giving details on a given provider.
What I want:
I have a button that submits a form and makes a new provider. This works. But I also want this button to forward the user to that new provider's detail page.
I have a function running that prints the id of the new provider to the console when it is made, I just can't figure out how to forward the user to the appropirate location.
Note:
I can add this to the function:
window.location = "/dashboard.html#!/providerDetail/" +id;
And it will take me to the new page, but I get this impression that this is a bad route to take.
Don't manually change the URL in AngularJS application, do it in Angular way so that you don't need to worry about manually kick of digest cycle. From function you could call $state.go method to navigate between states.
function myFunction(id) {
$state.go('providerDetail', { id: id})
}
Note: Please make sure you inject $state inside you controller.

angularjs fire promise and redirect to external URL before it resolves [duplicate]

I'm writing a small script to capture link clicks and save the link's URL into a database table in order to track how many times each link on a particular page is clicked. The links are to external sites.
So, I capture the click event in my JavaScript function, use jQuery to post to a PHP page that saves the data in MySQL, and then the JavaScript function redirects the user to the URL of the link they clicked on.
The problem I'm running into is that it seems like the post never completes because the of redirect. I've worked around this by calling the redirect inside the post callback, but that adds a few second delay because it doesn't get called until after the post completes. I'm looking for a way to just post the data and redirect the user immediately, regardless of whether or not the post succeeds or fails.
This is the current code with the workaround. It works fine, but adds the delay:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
},
function( response )
{
window.location = clicked;
}
);
event.preventDefault();
}
And this is what I'd like to do, but when I try it never completes:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
}
);
window.location = clicked;
event.preventDefault();
}
jQuery doesn't provide a callback for what you're looking for. Here are the available ready states:
Value State Description
0 UNSENT open()has not been called yet.
1 OPENED send()has not been called yet.
2 HEADERS_RECEIVED send() has been called, and headers and status are available.
3 LOADING Downloading; responseText holds partial data.
4 DONE The operation is complete.
You're looking for readystate 2, as that's the earliest you're aware of the fact that the server received the message.
This should get you off the ground:
var xhr = new XMLHttpRequest();
xhr.open("POST", clicked);
xhr.onreadystatechange = function() {
if (xhr.readyState >= 2) window.location = clicked;
};
xhr.send($('#track-click-post-url').attr('value'));
https://developer.mozilla.org/en/XMLHttpRequest for further reading.
Why do you post using Javascript when you are going to load a page any way?
Just update the db with the link clicked on the new page.
Perhaps using the referrer URL to track on what page the click was.
Or some other solution to get on what page the click was (e.g. url param) or some other way.
When you leave a page, all pending requests are killed, and the new page loads. The 1st way is the correct way. Yes, there will be a delay when a link is clicked, that's because the POST request is running.
You can't run a request in the background of a page, if the user is not on that page.
Maybe you can save the link URL in a cookie, and put it into the DB when the next page loads.

Loading message shown on $routeChangeStart disappears as soon as LESS CSS starts compiling

I've created a pretty heavy AngularJS application which has many dependencies (JS libraries and LESS css). When the application URL is hit, it determines the route based on login status and redirects to login route if not logged in. The problem is, until the route is redirected to, and the HTML is loaded, the page remains completely blank for almost 4-5 seconds, which looks really confusing.
I tried to implement some message using $routeChangeStart but it's not giving me the desired results. I want the 'loading..." message as soon as URL is hit and until app is routed and HTML is fully loaded. But the message is disappearing after a couple of milliseconds.
$rootScope.$on('$routeChangeStart', function() {
$rootScope.layout.loading = true;
});
$rootScope.$on('$routeChangeSuccess', function() {
$rootScope.layout.loading = false;
});
UPDATE: The problem seems to be the LESS CSS which is being compiled and loaded to get the page ready. The loading indicator text correctly works without LESS CSS (see this Plunker)
In the actual application, I have put the loading indicator text after the body tag, and there are many JS scripts (including LESS.js) after the indicator text. The loading indicator shows until LESS starts compiling, and disappears as after compilation starts. Any solution to this?
I believe .run() method of angular can solve your issue, Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kick start the application. It is executed after all of the services have been configured and the injector has been created.
You can try the following to show/hide loader when your application is loading.
.run(['$location', function ($location) {
// if your application URL os https://myApplication/Login/loginStatus
if ($location.path() === '' && $location.$$absUrl.indexOf('/loginStatus') > -1) {
// show loading
// some code here to return route based on login status for example,
var promise = getLoginStatus();
promise.then(function (result) {
// redirect to the route as per login status
$location.path(result); //Where result is route url.
// hide loading
});
}
}]);

Resources