How to test the rendered text of a mdToast with Protractor? - javascript

My workmate is working with Angular Material and he's using the mdToast this way: (in the else statements)
$scope.login = () => {
$scope.loading = true;
authService.login($scope.username, $scope.password, (result) => {
if (result) {
if (result === true) {
$location.path('/');
} else {
$mdToast.show($mdToast.simple().textContent($filter('capitalize')($filter('translate')('passwordNotValid'))).position('top right').hideDelay(3000));
$scope.loading = false;
$("#input_username_login").focus();
}
} else {
//error callback from server
$mdToast.show($mdToast.simple().textContent($filter('capitalize')($filter('translate')('passwordNotValid'))).position('top right').hideDelay(3000));
$scope.loading = false;
$("#input_username_login").focus();
}
});
};
I need to test the text of the resulted toast but it seems like Angular Material's mdToast uses ng-if. My workmate hasn't any HTML code for the toast (he's just using the controller above to generate it) even so when the toast is triggered the next code appears for a few seconds in the DOM:
screenshot of the generated md-toast
I've tried, among other things, slowing down the toast's disappearance with browser.wait, waitForAngular and so on, but didn't work. I'm stuck with:
it("should show error toast in English when user fails login", function(){
login.email_address_field.sendKeys('random_user#correo.com');
login.password_field.sendKeys('hardest_pass_123');
login.login_button.click();
expect(element(by.tagName('md-toast')).element(by.tagName('span')).getText()).toBe('Incorrect username and/or password');
});

I found a solution using this answer as an example. My spec is:
it("should show error toast in English when user fails login", function(){
login.email_address_field.sendKeys('random_user#correo.com');
login.password_field.sendKeys('hardest_pass_123');
login.login_button.click();
browser.ignoreSynchronization = true;
browser.sleep(1000);
expect(element(by.tagName('md-toast')).element(by.tagName('span')).getText()).toBe('Incorrect username and/or password');
browser.ignoreSynchronization = false;
});

You can use ExpectedConditions to make your script wait till the toaster message is displayed.Once the toaster is displayed then you can validate the message as well.
var EC = protractor.ExpectedConditions;
browser.wait(EC.visibilityOf(element(by.tagName('md-toast'))),5000);
expect(element(by.tagName('md-toast')).element(by.tagName('span')).getText()).toEqual('Incorrect username and/or password');

This spec worked for me and it does not use sleeps. The important thing to note here is that the browser.ignoreSynchronization flag must be set while the browser is waiting. Due to the asynchronous nature of the browser.wait, changing ignoreSynchronization must be done after the browser.wait promise resolves or else it could have no effect.
// a close button that appears on the md-toast template
const closeToastButton = $('[data-automation="toast-close"]')
const cond = protractor.ExpectedConditions
function waitToastShow() {
return browser.wait(cond.elementToBeClickable(closeToastButton), 5000)
}
function waitToastHide() {
return browser.wait(cond.invisibilityOf(closeToastButton), 5000)
}
screenshot = name => browser.takeScreenshot().then(/* save fn */)
describe('a suite ... ', () => {
it('takes screenshots of an md-toast once shown and after hidden', function () {
// ... actions that launch an md-toast using $mdToast.show({ ... })
browser.ignoreSynchronization = true
waitToastShow().then(() => {
screenshot('toast-showing.png')
waitToastHide().then(() => {
screenshot('toast-hidden.png')
browser.ignoreSynchronization = false;
})
})
});
}

Related

How to force consecutive if blocks to execute one after the other?

I am trying to execute two if blocks one after the other in typescript.Both the if blocks consist of a service. But the condition for the second if block depends on a flag being set to true in the first if block.I know this is a little confusing but please take a look at the code below for a better understanding
export class Component{
condition1: boolean
constructor(private confirmationService: ConfirmationService) {}
submit()
{
this.condition1 = false;
if (somecondition)
{
if (this.condition1 == false)
{
this.confirmationService.confirm({
message: Do you want to proceed?
accept()
{
// redirect to other page
}
reject()
{
this.condition1 = true;
}
})
}
if (this.condition1 == true)
{
this.confirmationService.confirm({
message: Do you want to quit?
accept()
{
//do something
}
reject()
{
//do something
})
}
}
}
}
}
So basically what is happening here is on clicking the SUBMIT button ,a confirm dialog box appears, this box will display different messages depending on the condition. If condition one is false then the dialog box displays the first message.If we click on YES on the dialog box then accept() is called , if we click on NO then reject() is called and there in reject() the condition1 is set to true.As soon as this condition is set to true we want the current dialog box to close(which is, that we want to come out of the service)and immediately reappear again with the second message in it(that is the second if condition is executed and the same service with a different message is called).How can I achieve this?
since you didn't share what this.service is, I only can assume it is a wrapper around window.confirm.
Every time you call the function submit you set condition1 = false; which makes the first check if (condition1 == false) useless.
Since confirm always only returns true or false you can do it like this
function submit() {
const redirect = confirm('Do you want to proceed?');
if (redirect) {
self.location.href = 'bla/bla/bla';
} else {
const quit = confirm('Do you want to quit?');
if (quit) {
console.log('I want to quit');
} else {
console.log('I do not want to quit');
}
}
}
submit();
I hope this is what you were looking for. Once again, it is a hard guess without all the code.
EDITED:
After you edited your Post it makes more sense. I don't know much about Angular but the same logic should work though.
const redirect = this.confirmationService.confirm({
message: 'Do you want to proceed?',
accept: () => true,
reject: () => false
});
if (redirect) {
self.location.href = 'bla/bla/bla';
} else {
const quit = this.confirmationService.confirm({
message: 'Do you want to quit?',
accept: () => true,
reject: () => false
});
if (quit) {
console.log('I want to quit');
} else {
console.log('I do not want to quit');
}
}

testing all the links on a page using protractor

I've been able to work this out in java but so far I'm only able to open the webpage using jasmine js. In java, all the anchor tag links can be saved in List and then each link can be navigated using the browser driver object. But in jasmine js, I'm unable to store those links in an array. Here's what I've tried to do:
describe('demo', function()
{
it('mydemo', function()
{
browser.ignoreSynchronization = true;
browser.driver.get('https://www.google.co.in');
var array = [];
array.push(browser.findElement(by.xpath("//a[#href]")));
for(var i=0; i<array.length; i++)
{
expect(browser.navigate().to(array[i]));
}
}
}
A new browser window pops up, navigates to google and then closes. There seems to be a timeout issue. Using browser.ignoreSynchronization = true, the server ignores it as an angular application but still the timeout issues persists. Any suggestions?
To get all the links, call getAttribute on an ElementArrayFinder. It will return a Promise which once resolved will give you all the links.
Then call filter to exclude the dynamic links (href="javascript:...) and forEach to iterate each link:
browser.ignoreSynchronization = true;
$$("a[href]").getAttribute("href")
.then(links => links
.filter(link => !/^javascript/.test(link))
.forEach(link => {
console.log(link);
browser.driver.get(link);
})
);
Another and quicker way is to get all the links with execute script with a single call to the browser:
browser.ignoreSynchronization = true;
browser.driver.executeScript("return [].map.call(document.links, function(e){return e.href})")
.then(links => links
.filter(link => !/^javascript/.test(link))
.forEach(link => {
console.log(link);
browser.driver.get(link);
})
);
See following code.
$$('a').map(function(link) {
return link.getAttribute("href").then(function (href) {
return href.replace(/https\:\/\/app\.perflectie\.nl\//g, localhost);
});
}).then(function(links) {
links.forEach(function(link) {
browser.get(link);
expect(browser.getCurrentUrl()).not.toContain('/Error/');
});
});
For more innformation go to following links.
Link 1
Link 2
Hope this helps. :)
it('link elements', function () {
browser.ignoreSynchronization = true;
browser.get('https://www.google.co.in');
element.all(by.tagName('a')).each(function (elem) { // this is the important step, rest you can do whatever you want inside this
elem.getText().then(function (val) {
console.log('#### : ' + val)
})
})
});

Angular confirmation box style

After I login in my app which I made in Angularjs, first step is that initial function show confirmation box. That is regular confirmation box, without any style. My question is how I can stylize that box with CSS, and add some text too, if my function is :
function versionCheck() {
if ($window.confirm("Are you sure?")) {
vm.starting = true;
dataService.restartVersion()
.then(function onSuccess(data) {
vm.data = data.response;
}).catch(function onReject(response) {
}).finally(function () {
vm.starting = false;
});
}
}
Again I will remind that I immediately start with this function after login, without press button or something else, automatically.
Any helps?
//---------------------------------------------------------------------------------------------------------------
# This is the Angular Material Design way of customizing dialog boxes.
# $mdDialog.confirm() and $mdDialog.show will solve this issue.
# I would create a totally different function to handle the dataService
# And have the versionCheck() just handling the confirmation dialog
# as shown below.
function versionCheck() {
var confirm = $mdDialog.confirm()
.title('Cancel confirmation')
.textContent('Are you sure?')
.ariaLabel('Are you sure?')
.ok('Ok')
.cancel('Cancel');
$mdDialog.show(confirm).then(
function() {
$state.go('login');
);
}
);
}
//---------------------------------------------------------------------------------------------------------------

"$apply already in progress" when opening confirm dialog box with Firefox

I am (sometimes) getting a weird $apply already in progress error when opening a confirm dialog box in the following and innocent looking situation :
var mod = angular.module('app', []);
mod.controller('ctrl', function($scope, $interval, $http) {
$scope.started = false;
$scope.counter = 0;
// some business function that is called repeatedly
// (here: a simple counter increase)
$interval(function() {
$scope.counter++;
}, 1000);
// this function starts some service on the backend
$scope.start = function() {
if(confirm('Are you sure ?')) {
return $http.post('start.do').then(function (res) {
$scope.started = true;
return res.data;
});
};
};
// this function stops some service on the backend
$scope.stop = function() {
if(confirm('Are you sure ?')) {
return $http.post('stop.do').then(function (res) {
$scope.started = false;
return res.data;
});
};
};
});
// mock of the $http to cope with snipset sandbox (irrelevant, please ignore)
mod.factory('$http', function ($q) {
return {
post: function() {
return $q.when({data:null});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
<button ng-disabled="started" ng-click="start()">Start</button>
<button ng-disabled="!started" ng-click="stop()">Stop</button>
<br/><br/>seconds elapsed : {{counter}}
</div>
</div>
The error message is :
$rootScope:inprog] $apply already in progress http://errors.angularjs.org/1.2.23/$rootScope/inprog?p0=%24apply
And the callstack is :
minErr/<#angular.js:78:12
beginPhase#angular.js:12981:1
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12770:11
tick#angular.js:9040:25
$scope.start#controller.js:153:8
Parser.prototype.functionCall/<#angular.js:10836:15
ngEventHandler/</<#angular.js:19094:17
$RootScopeProvider/this.$get</Scope.prototype.$eval#angular.js:12673:16
$RootScopeProvider/this.$get</Scope.prototype.$apply#angular.js:12771:18
ngEventHandler/<#angular.js:19093:15
jQuery.event.dispatch#lib/jquery/jquery-1.11.2.js:4664:15
jQuery.event.add/elemData.handle#lib/jquery/jquery-1.11.2.js:4333:6
To reproduce :
use Firefox (I could not reproduce it with Chrome or IE)
open the javascript console
click alternatively the start and stop buttons (and confirm the dialogs)
try multiple times (10-20x), it does not occur easily
The problem goes away if I remove the confirm dialog box.
I have read AngularJS documentation about this error (as well as other stackoverflow questions), but I do not see how this situation applies as I do not call $apply nor do I interact directly with the DOM.
After some analysis, it seems to be a surprising interaction between the $interval and modal dialog in Firefox.
What is the problem ?
The callstack shows something strange : an AngularJS function tick is called within the controller's start function. How is that possible ?
Well, it seems that Firefox does not suspend the timeout/interval functions when displaying a modal dialog box : this allows configured timeout and intervals callback to be called on the top of the currently executing javascript code.
In the above situation, the start function is called with an $apply sequence (initiated by AngularJS when the button was clicked) and when the $interval callback is executed on the top the start function, a second $apply sequence is initiated (by AngularJS) => boom, an $apply already in progress error is raised.
A possible solution
Define a new confirm service (adapted from this and that blog posts) :
// This factory defines an asynchronous wrapper to the native confirm() method. It returns a
// promise that will be "resolved" if the user agrees to the confirmation; or
// will be "rejected" if the user cancels the confirmation.
mod.factory("confirm", function ($window, $q, $timeout) {
// Define promise-based confirm() method.
function confirm(message) {
var defer = $q.defer();
$timeout(function () {
if ($window.confirm(message)) {
defer.resolve(true);
}
else {
defer.reject(false);
}
}, 0, false);
return defer.promise;
}
return confirm;
});
... and use it the following way :
// this function starts some service on the backend
$scope.start = function() {
confirm('Are you sure ?').then(function () {
$http.post('start.do').then(function (res) {
$scope.started = true;
});
});
};
// this function stops some service on the backend
$scope.stop = function() {
confirm('Are you sure ?').then(function () {
$http.post('stop.do').then(function (res) {
$scope.started = false;
});
});
};
This solution works because the modal dialog box is opened within an interval's callback execution, and (I believe) interval/timeout execution are serialized by the javascript VM.
I was having the same problem in Firefox. Using window.confirm rather than just confirm fixed it for me.

NightwatchJS .waitForElementPresent abortOnFailure not working

I'm using NightwatchJS with NodeJS: http://nightwatchjs.org/api
I have a modal dialog, which may or may not appear. It has a #close_button that needs to be clicked (if the modal does appear) to continue.
I set the abortOnFailure parameter of waitForElementPresent to false so the script continues if the modal does not appear. However I can't get it to work.
Any suggestions?
module.exports = {
"Test" : function (browser) {
browser
.url("http://domain.com/")
.waitForElementPresent('#close_button', 5000, false, function() {
this.click('#close_button')
})
.setValue('#username', 'test#email.com')
//more code here
.end(); //does end() go here or inside .waitForElementPresent() above?
}
}
abortOnFailure works fine, however waitForElementPresent has a bug now in which the callback you passed it's not called in the correct context. That will be fixed.
In the mean time you can write your test like this, with placing the click outside, which is the same thing and looks cleaner:
module.exports = {
"Test" : function (browser) {
browser
.url("http://domain.com/")
.waitForElementPresent('#close_button', 5000, false)
.click('#close_button')
.setValue('#username', 'test#email.com')
//more code here
.end(); // end() goes here
}
}
I ran into something similar, I was waiting for an iframe to be present. I created a function to actually close it:
pageObject function:
Home.prototype.closeIframe = function(browser) {
var self = this;
console.log('Checking for iframe');
this.browser
.isVisible(iframeSelectors.iframe, function(result) {
if (result.value === true) {
self.browser
.log('iframe visible')
.frame(iframeSelectors.name)
.waitForElementVisible(iframeSelectors.closeLink)
.click(iframeSelectors.closeLink)
.assert.elementNotPresent(iframeSelectors.iframe)
.frame(null)
.pause(2000); //allow for proper frame switching
} else {
console.log('iframe is not visible');
}
});
return this;
In my test I wait for the page to fully load before executing the above function.

Categories