protractor test before initial promises resolve - javascript

I want to test the state of the controller after initialization completes, but before promises resolve.
I have a button which is disabled (by class) until loading completes:
<button ng-class="{disabled: isLoading}">Show</button>
When the controller is initialized, an ajax call is made and when it resolves, the isLoading is set to false:
$scope.isLoading = true;
$http.get('/list')
.then(function(response) {
$scope.list = response.data;
$scope.isLoading = false;
});
When I test the button using protractor, there is no disabled class.
it('should enable the button after load completes', function() {
element(by.buttonText('Show')).getAttribute('class')
.then(function(buttonClasses) {
expect(buttonClasses).toContain('disabled');
});
});
I modified the button to have another class, just to see I'm not going bonkers:
<button ng-class="{disabled: isLoading, enabled: !isLoading}">Show</button>
and the test:
it('should show the select tables list after clicking the Change button', function() {
element(by.buttonText('Show')).getAttribute('class')
.then(function(buttonClasses) {
expect(buttonClasses).toContain('enabled');
});
});
Now the test passes.
I'm guessing that waitForAngular is part of the controller initialization process. How do I test the state of the button Before the promise resolves?

You can set browser.ignoreSynchronization = true before finding the element/class to not wait for promises in the app to resolve
it('should show the select tables list after clicking the Change button', function() {
browser.ignoreSynchronization = true;
expect(element(by.buttonText('Show')).getAttribute('class')).toContain('disabled');
browser.ignoreSynchronization = false;
});
Note you rarely need to use .then callbacks for expect, since expect handles unwraping of the promises.

Related

Prevent MatDialog From Closing When Clicked Outside with pending changes

I want to prevent close on outside when there are pending changes.
I something like this, but with no result.
this.dialogRef.beforeClosed().subscribe(() => {
this.dialogRef.close(false);
//some code logic
//...
});
The disableClose on MatDialog have to stay false
Initially, while opening dialog, you can pass 'disableClose' as true, and later manually close dialog on backdrop click or escape click if there are no pending changes.
this.dialog.open(DialogComponent, { disableClose: true });
dialogRef.backdropClick().subscribe( () => {
if(!pendingChanges) dialogRef.close();
// else do nothing
});
Depending on the case, you can initially set disableClose as false, so that user can close it if there is no pending changes, depending on what that pending change is, if it is an async call for example, you can set the disableClose as true.
You can also then inject the MatDialogRef into the component itself and manually toggle disableClose per your requirements, so something like this:
constructor(private matDialogRef: MatDialogRef<WhateverYourDialogIsCalled>) {}
then in a async call it could be:
onSubmit() {
this.matDialogRef.disableClose = true;
this.myService.doSomething().subscribe(_ => {
this.matDialogRef.disableClose = false;
});
}

AngularJS needs to execute a function two times to show the results

I have a navigation bar which has a drop-down menu item Log Out which calls the ngPostLogOut() function.
In app.js
.when("/logout", {
controller: 'AuthController',
templateUrl: "client/html/auth/logout.html"
});
AuthController
$scope.ngPOSTLogOut = function() {
if ($rootScope.user) {
$rootScope.user = null;
}
if ($window.sessionStorage) {
$window.sessionStorage.clear();
alert('Entered..');
}
alert('Before HTTP');
$http.post('server/auth/logout.php')
.then(function(result) {
$scope.logout = result.data;
});
alert('After HTTP');
/*
$timeout(function() {
$location.path('/');
}, 10000);
*/
};
logout.html
<div ng-controller="AuthController as auth">
<p ng-show='user == null' class="text-center">{{logout}}</p>
<br>
<button ng-click='ngPOSTLogOut()' class="btn btn-default btn-block">Angular To PHP</button>
Now, if a person clicks the Log Out item from the drop-down in the navbar then the function is called. I know this because I have set up alerts and they do pop up. But, the 'echo' from the login.php doesn't get featured. But, the odd thing is is that if I press the Angular to PHP button which also calls the ngPostLogOut() function, the function completes perfectly and as intended.
My guess
My guess is that ngRoute forces Angular to prioritize the HTML template switch making it so that the ngPOSTLogOut() function's parametres get ignored or dismissed.
The entire project on GitHub
https://github.com/AquaSolid/RAMA_Angular_PHP
Change this:
<li ng-click='ngPOSTLogOut()' ng-show='user != null'>Log Out</li>
to this:
<li ng-show="user != null"><a ng-click="ngPOSTLogOut()">Log Out</a></li>
And consider using ng-if rather than ng-show.

Chaining events in AngularJS

I'm trying to call a function after a popup automatically closes after 1 second.
This is my code:
$timeout(function() {
var closeit = myPopup.close();
closeit.then(function() {
$scope.dosomething();
});
}, 1000);
The dosomething function is never called. I'm new to AngularJS, anyone who can help me with this?
$ionicPopup - $ionicPopup.show(options) documentation:
Returns: object A promise which is resolved when the popup is closed.
Has an additional close function, which can be used to
programmatically close the popup.
var myPopup = show(options); // when you create a popup with $ionicPopup, you get a promise for the close event
myPopup.then(function() { // add a callback to the promise when it's fulfilled - ie the popup was closed
$scope.dosomething();
});
$timeout(function() {
myPopup.close();
}, 1000);

Kendo UI AngularJs grid directive, undefined

In a ticket entry page, I have a main ticketEntry.html page, which contains one grid for the lines and one for the payments.
When ticketEntry.html is loaded, it must first retrieve the ticket view model (via ajax calls to Web API). The line and payment grid cannot retrieve their data until the ticket view model has been received.
In my current solution, I have to use $timeout in the controller for ticketEntry.html for this to work. I am looking for a cleaner way.
Extracts from ticketEntry.html:
<div ng-controller="ticketLineController">
<div id="ticketLineGridDiv" kendo-grid="ticketLineGrid" k-options="ticketLineGridOptions"></div>
</div>
...
<div ng-controller="ticketPaymentController">
<div id="ticketPaymentGridDiv" kendo-grid="ticketPaymentGrid" k-options="ticketPaymentGridOptions"></div>
</div>
In the controller for ticketEntry.html, I have this:
$timeout(function () {
ticketService.getTicket(ticketId).then(
function(ticket) {
$scope.initPos(ticket);
},
...);
}, 500);
$scope.initPos = function(ticket) {
$scope.ticket = ticket; <-- $scope.ticket is used by the line and payment grid
$scope.$broadcast('PosReady'); <-- Tell the payment and line controllers to load their grids
}
As you can see, I am using $timeout to delay for 500ms, then I get the ticket view model and broadcast to the line and payment controller that they now can load their grids.
Here is the listener in the line controller:
$scope.$on('PosReady', function (event) {
$scope.ticketLineGrid.setDataSource(getGridDataSource());
$scope.ticketLineGrid.dataSource.read();
});
The problem is that if I do not use $timeout in the ticket entry controller, $scope.ticketLineGrid is sometimes undefined here (same thing with the payments controller).
I have tried using angular.element(document).ready(function () {...} instead of $timeout in the ticket entry controller, but that did not handle the issue.
How do I know when $scope.ticketLineGrid (for example) has been created/defined?
What is the proper way of handling this kind of scenario?
Update 9/27/2014, to provide more data on how the ticket line grid gets initialized:
In the AngularJs directive in ticketEntry.html, the k-options specifies the definition object for the grid:
<div id="ticketLineGridDiv" kendo-grid="ticketLineGrid" k-options="ticketLineGridOptions"></div>
ticketPaymentGridOptions is just an object with properties that defines the grid:
$scope.ticketPaymentGridOptions = {
autoBind: false,
height: 143,
columns: [
{
field: "payCode", title: "PayCode",
},
{
field: "amount", title: "Amount", format: "{0:n2}", attributes: { style: "text-align:right" },
},
],
pageable: false,
...
};
Update 9/29/2014: This is the solution I went with, based on suggestion by Valentin
I use two watches - one in the child scope where the ticketLineGrid lives:
$scope.$watch('ticketLineGrid', function (newVal) {
if (angular.isDefined(newVal)) {
$scope.ticketControl.lineGridReady = true;
}
});
This watch sets the parent property $scope.ticketControl.lineGridReady = true once the grid has been initialized.
The parent (ticketEntryController) has watches for lineGridReady:
$scope.$watch('ticketControl.lineGridReady', function (gridReady) {
if (gridReady) {
$scope.loadPage();
}
});
$scope.loadPage = function () {
ticketService.getTicket(ticketId).then(
function (ticket) {
$scope.initPos(ticket);
}
...
}
Not as clean as I would have liked it, but certainly better than using $timeout...
How do I know when $scope.ticketLineGrid (for example) has been created/defined?
You could use a $scope.$watch statement :
$scope.$watch('ticketLineGrid', function (newVal, oldVal) {
if(angular.isDefined(newVal)){
// do something with it
}
})
However, in my view the good way to do this is to retrieve the data not from a scope property, but from a promise. I would use only promises and no events at all for this :
var ticketPromise = ticketService.getTicket(ticketId);
ticketPromise.then(function (ticket) {
$scope.ticket = ticket;
});
// you know that part better than I do
var ticketLineGridPromise = ...;
$q.all([ticketPromise, ticketLineGridPromise])
.then(function (realizations) {
var ticket = realizations[0], ticketLineGrid = realizations[1];
$scope.ticketLineGrid.setDataSource(getGridDataSource());
$scope.ticketLineGrid.dataSource.read();
})
I can't be more precise because it's not clear from your code what initializes ticketLineGrid.
Finally, in many cases it's very handy to use resolve clauses in your route declaration.

Protractor tests inconsistently passing / failing for AngularJS app

My Protractor e2e tests are inconsistently passing and failing.
It seems this could be due to asynchronous javascript, as discussed here:
Protractor : How to wait for page complete after click a button?.
However, here it's mentioned that Protractor tests automatically execute sequentially / synchronously:
https://github.com/angular/protractor/issues/909
My test script:
describe('Login', function() {
var ptor;
beforeEach(function() {
browser.get('https://127.0.0.1:8443');
ptor = protractor.getInstance();
element(by.id('splash')).click();
browser.ignoreSynchronization = true; // <-- to proceed beyond splash screen
});
describe('with correct email and password', function() {
beforeEach(function() {
element(by.id('email')).sendKeys('admin#email.com');
element(by.id('password')).sendKeys('adminpassword');
element(by.id('loginButton')).click();
});
afterEach(function() {
ptor.findElement(by.id('logout')).then(function(elem) {
elem.click();
});
});
it('does not show alert', function() { // <-- sometimes passes, sometimes fails
expect(browser.isElementPresent(by.css('.alert-danger'))).toBe(false);
});
it('changes route to /admin', function() { // <-- sometimes passes, sometimes fails
expect(browser.getCurrentUrl()).toMatch(/\/admin/);
});
});
});
In the two tests above, either both tests will pass, or one/both of the tests will fail with these messages:
Failures:
1) Login with correct email and password does not show alert
Message:
NoSuchElementError: no such element
...
==== async task ====
WebDriver.findElement(By.id("logout"))
...
or
Failures:
1) Login with correct email and password changes route to /admin
Message:
NoSuchElementError: no such element
...
==== async task ====
WebDriver.findElement(By.id("logout"))
...
Thoughts / help much appreciated.
I was able to resolve the issue based on the following:
Avishay's answer here about adding ptor.waitForAngular():
No element found using locator: by.model() error
Changing browser.get to ptor.get, as in Harri Siirak's answer here:
Protractor times out waiting for sync with page when using $resource
juliemr's comment here about ignoreSynchronization being an instance variable, and changing browser.ignoreSynchronization=true to ptor.ignoreSynchronization=true:
https://github.com/angular/protractor/issues/49
glepretre's answer here about using .then():
Protractor : How to wait for page complete after click a button?
As mentioned by Nguyen Vu Hoang's comment to the original question, I am testing a pure Angular app with what I think is pure Protractor (no webdriver calls). I know ptor.ignoreSynchronization=true should not be required in this case, but for some reason, the tests are not proceeding at button click without this setting.
My new spec:
describe('Login', function() {
var ptor;
beforeEach(function() {
ptor = protractor.getInstance();
ptor.ignoreSynchronization = true;
ptor.waitForAngular();
ptor.get('https://127.0.0.1:8443');
ptor.findElement(by.id('splash')).then(function(elem) {
elem.click();
});
});
describe('with correct email and password', function() {
beforeEach(function() {
ptor.findElement(by.id('email')).then(function(elem) {
elem.sendKeys('admin#email.com');
});
ptor.findElement(by.id('password')).then(function(elem) {
elem.sendKeys('adminpassword');
});
ptor.findElement(by.id('loginButton')).then(function(elem) {
elem.click();
});
});
afterEach(function() {
ptor.findElement(by.id('logout')).then(function(elem) {
elem.click();
});
});
it('does not show alert', function() {
expect(ptor.isElementPresent(by.css('.alert-danger'))).toBe(false);
});
it('changes route to /admin', function() {
expect(ptor.getCurrentUrl()).toMatch(/\/admin/);
});
});
});
There is also an another technique to make your tests more stable: Explicit Waits and Expected Conditions (docs).
I've found using Expected Conditions especially useful when testing against non-angular pages or angular applications that have a lot of animations involved.
For example, you can wait for an element to be clickable before making a click:
var EC = protractor.ExpectedConditions;
var link = element(by.id("mylink"));
browser.wait(EC.elementToBeClickable(link), "10000", "The link is still not clickable");
link.click();
There are also other built-in Expected Conditions, such as:
presenseOf()
visibilityOf()
alertIsPresent()
textToBePresentInElementValue()
etc
And, it is easy to write a custom Expected Condition, example use case:
Testing link style changes
You can also combine Expected Conditions using and, or and not, e.g.:
var urlChanged = function() {
return browser.getCurrentUrl().then(function(url) {
return url != 'http://www.angularjs.org';
});
};
// condition to wait for url to change, title to contain 'foo', and $('abc') element to contain text 'bar'
var condition = EC.and(urlChanged, EC.titleContains('foo'), EC.textToBePresentInElement($('abc'), 'bar'));
$('navButton').click();
browser.wait(condition, 5000); //wait for condition to be true.
browser.ignoreSynchronization = true; has a global effect for all your tests. you may have to set it back to false, so protractor waits for angular to be finished rendering the page. e.g. in or before your second beforeEach function

Categories