Error: Wait timed out after 3005ms
I used window.scrollTo(0,200) for scrolling the web page down. The code is as follows:
browser.wait(function() {
browser.executeScript('window.scrollTo(0,200);').then(function () {
})
}, 3000);
REQUIREMENT: NFAR after scroll down. It should scroll up after waiting for 3000ms.
To scroll up I wrote the code as follows
browser.wait(function() {
browser.executeScript('window.scrollTo(0,-200);').then(function () {
})
}, 10000);
Is multiple browser.wait causing errors? Please suggest some best way to do the same.
browser.wait does not 'wait' after performing some action. It is a function to keep retrying the function inside until it returns true OR reaches the specified timeout (if it reaches the timeout it will throw an error).
Since you are not returning anything inside your function, it will always return 'undefined' and thus never be true, and will throw the error that it timed out in 3000 ms.
The code you probably want uses browser.sleep
browser.executeScript('window.scrollTo(0,200);').then(function() {
browser.sleep(3000);
}).then(function() {
browser.executeScript('window.scrollTo(0,0);');
});
Finally I found my solution!!
The following code I found helpful to remove the error.
var elm = element.all(by.css('.your-css-class')).get(9);
browser.executeScript("arguments[0].scrollIntoView();", elm.getWebElement());
elm.click();
Basically this allows you scroll into your view..
There is an open issue on protractor relating to this.
Perhaps this will help you:
browser.executeScript('window.scrollTo(0,200);').then(function () {
browser.executeScript('window.scrollTo(0,0);').then(function () {
// ...
})
})
Related
I use Protractor 4.0.14 on an Angular 1.6.3 website.
I execute two identical tests. The first one will succeed but not the second one. Any idea ?
it('Should return a toast error on inferior amount', function () {
$$('#purposeList input.amount').first().clear().sendKeys('4999');
$$('button[type="submit"]').get(0).click();
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear();
$$('button.toast-close-button').each(function (item) {
item.click();
});
expect($('button.toast-close-button').isPresent()).toBe(false);
});
it('Should return a toast error on inferior amount BIS', function () {
$$('#purposeList input.amount').first().clear().sendKeys('4999');
$$('button[type="submit"]').get(0).click();
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear();
$$('button.toast-close-button').each(function (item) {
item.click();
});
expect($('button.toast-close-button').isPresent()).toBe(false);
});
The field is provided with a wrong amount so it throws a toast-error when I submit. When I do it manually, I get the toast-error. When doing it with protractor, only the first of the identical tests will pass. On the other, it looks like the toast is never launch or closed really quickly.
I tried sleeps, ignoreSynchronisation with browser wait. I tried to replace the $timeout by $interval in the service dealing with error toasts (although other $timeout could be present). It didn't change anything.
It is actually the first expect of the second it that is failing and there is no actual help from the error, it's just an expect at false instead of true :
Message:
Expected false to be true.
Edit :
So #LostJon, by handling every promises, do you mean like this :
it('Should return a toast error on inferior than authorized purpose amount on step 1 BIS', function () {
$$('#purposeList input.amount').first().clear().then(function() {
$$('#purposeList input.amount').first().sendKeys('4999').then(function () {
$$('button[type="submit"]').get(0).click().then(function () {
expect($('div.toast.toast-error').isPresent()).toBe(true);
element.all(by.model('purpose.amount')).first().clear().then(function () {
$$('button.toast-close-button').each(function (item) {
item.click();
}).then(function () {
expect($('button.toast-close-button').isPresent()).toBe(false);
});
});
});
});
});
});
I tried it, same result.
Edit :
Apparently it was an ignoreSynchronization = true that was inside the wait instead of before. I didn't figure it out before because I also set browser.manage().timeouts().implicitlyWait(2000), so without synchronization, we were still waiting for the elements but it was not accurate.
so, the below line returns a promise.
$$('button[type="submit"]').get(0).click();
Your code is running synchronously, when you should be handling the promise resolve from click(). In fact, your line that calls clear() also returns a promise, so that needs to be handled as well.
It was an ignoreSynchronization = true that was inside the wait instead of before. I didn't figure it out before because I also set browser.manage().timeouts().implicitlyWait(2000), so without synchronization, we were still waiting for the elements but it was not accurate.
I have a service Home and inside it, I have selectInterval function like so:
Home.selectInterval = function () {
var interval = $interval(function () {
if (angular.element('.ui-select-toggle').length > 0) {
angular.element('.ui-select-toggle').bind('click', function () {
if (angular.element('.ui-select-choices-row').length > 0) {
angular.element('.ui-select-choices-group').mCustomScrollbar();
}
});
$interval.cancel(interval);
}
}, 50);
};
Right now I am testing it like this:
it('selectInterval() should be called.', function () {
Home.selectInterval();
$interval.flush(50);
$rootScope.$apply();
expect(Home.selectInterval).toHaveBeenCalled();
// need to test $interval as well
});
I want to test if function is called and also $interval worked fine. Right now It giving me this error.
Some of your tests did a full page reload!
I'm not sure if this test is what is causing your issue. Whenever I have seen that error it is because there is some code that is changing the url. Setting something like $window.location.href or $location.path('/new-path') will cause the phantomjs browser to do a page reload which there just isn't any support to handle right now.
If you find this is the issue, you just need to try to spy the method and it will never actually call it. That specifically works for $location.path('/new-path')
I got an
<input type="file" id="aircraftList" name="aircraftList" file-upload multiple/>
bound to a directive
angular.module("app.directives").directive('fileUpload', function () {
return {
scope: true,
link: function (scope, el, attrs) {
el.bind('change', function (event) {
scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});
}
};
});
I catch this event in a controller:
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
});
For some reason this works perfectly well in Chrome and Firefox, but fails in IE11 with the following error:
If I dont put the $apply, chrome is not updating the view, but IE is. If I put the $apply, Chrome works perfect and IE breaks.
Anyone knows what and why it goes wrong here?
Actually, chrome and FF javascript engines are very fast when compared to IE11 javascript engine.
Hence, when the $scope.$on("fileSelected" is triggered in chrome &
FF, the previous $digest loop will be completed at the time of
$scope.$apply is executed and hence no errors. As there is no
$digest cycle is running at this stage, we need another $digest
cycle to update the view with help of $scope.$apply and without
this, view won't be updated.
As IE is comparatively slow on the same scenario above,
$scope.$apply is throwing error as there is one $digest loop is
running currently. Hence, without $scope.$apply, the view will get
updated with help of the running $digest cycle.
When we use $timeout as said by other users, it will start executed once the current $digest cycle is completed and making sure to update the view with another $digest loop.
Hope it will clarify you :-)
$scope.$on("fileSelected", function (event, args) {
$timeout(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
});
Firstly, you're calling $apply from within an already executing $digest cycle. Chrome/FF may be fine for you, but that's really down to luck on your part. Really on this you're at the mercy of the user's PC performance. Angular will always trigger off it's own $digest cycle whenever it is transmitting events. Your $scope.$emit will be triggering $digest here.
You've got a few problems here though which are going to be tying everything up in knots, and will cause further problems of this kind. Normally there should be no need for you to trigger a $digest cycle unless you are responding to events triggered from outside Angular.
Your directive file-uploader seems far too dependent on your view model - it's even telling the controller which field it should be storing the returned data in. Rememember, that's the controllers job! I've changed your code around a little to ensure that there's no need to have two simultaneous $apply cycles, which eliminates your problem, and also to tidy up the code a little.
I've changed the directive to use two-way data-binding instead of emitting events via the rootscope - big improvement to performance, and encapsulation of functionality.
app.directive('testUploader', [function() {
return {
scope: {
ngModel: '='
},
link: function(scope, el) {
el.bind('change', function(event) {
if (event.target.files && event.target.files.length > 0) {
angular.forEach(event.target.files, function (newFile) {
scope.ngModel.push(newFile);
});
scope.$apply();
}
});
}
};
}]);
This vastly simplifies the controller which now has no need to handle client events - it simply mediates between the view model and any underlying data model up on your server
app.controller("testctrl", ['$scope', function($scope) {
$scope.data = {
aircraftList: []
};
}]);
See working JSFiddle here: https://jsfiddle.net/z2yLad92/27/
Hope this helps.
i don't know why both the browsers are behaving diffrently. But I know to get rid of this error and make it work
You have used $apply(). If you get $rootScope:inprog error, it means already a digest cycle is running. To avoid this error wrap your $apply function under a timeout condition.
like this,
$scope.$on("fileSelected", function (event, args) {
$timeout(function () {
$scope.$apply(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
},500);
});
I hope this works for you.
You stuck into this issue as your code try to trigger digest cycle before one got completed and that you are facing only in IE probably because of slow nature of IE. so my idea is to use $scope.$evalAsync
$scope.$evalAsync(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
$scope.$evalAsync will adds the supplied function to a queue that will be drained at the beginning of the next loop in a digest cycle.
I hope this work for you.
Thanks
You can apply scope by checking first
(!$scope.$$phase) ? $scope.$apply() : null;
It will not apply scope if it is already in progress.
I don't think any of the others answers here (at time of posting) are correct.
No doubt there is some sort of timing issue or similar between Chrome/FF vs IE11, but that isn't the fundamental problem here.
The fundamental problem is this:
el.bind('change', function (event) {
scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});
You shouldn't be emitting an AngularJS event, when you're not in an AngularJS digest cycle.
The code should be this:
el.bind('change', function (event) {
scope.$apply(function() {
scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});
});
You can then remove the $scope.$apply() call from your second piece of code.
Before that day I always made isolated small tests. But now I want run them in one scenario. And I have the strange error. Some tests can't work together.
For example.
First one:
beforeEach(function(){
browser.get('ng-components/examples/ps-grid-column-filter-range.html');
});
it('балун содержит текст', function () {
filter_field.click();
browser.actions().click(filter_field).perform();
browser.wait(function () {
return balloon_info.isPresent();
},5000).then(function () {
expect(balloon_text.getText()).toContain(balloon_contain_text);
expect(balloon_text.isDisplayed()).toBe(true);
}).thenCatch(function () {
expect(true).toBe(false);
});
console.log("ps-grid-column-filter-range_spec_1.1.с");
});
Second one:
beforeEach(function(){
browser.get('ng-components/examples/ps-grid-column-filter-range.html');
});
it('балун демонстрируется', function () {
filter_field.click();
browser.actions().click(filter_field).perform();
browser.wait(function () {
return balloon_info.isPresent();
},5000).then(function () {
expect(balloon_info.isDisplayed()).toBe(true);
}
,function (error) {
expect(false).toBe(true);
});
console.log("ps-grid-column-filter-range_spec_1.1.a");
});
When my tests isolated they working fine. But in group - they failing. What is my mistake? It is a problem with asynchronous?
Also, what is interesting is that some broken test hasn't method wait() in it.
browser.wait is non blocking so your second test probably runs while your first is still in progress. And since it is the same functionality they probably influence each others outcome.
Try putting them both in the same test and chain them with
.then()
You can make protractor blocking wait by following this example
Blocking wait
I'm trying to test the login page on my site using protractor.
If you log in incorrectly, the site displays a "toast" message that pops up for 5 seconds, then disappears (using $timeout).
I'm using the following test:
describe('[login]', ()->
it('should show a toast with an error if the password is wrong', ()->
username = element(select.model("user.username"))
password = element(select.model("user.password"))
loginButton = $('button[type=\'submit\']')
toast = $('.toaster')
# Verify that the toast isn't visible yet
expect(toast.isDisplayed()).toBe(false)
username.sendKeys("admin")
password.sendKeys("wrongpassword")
loginButton.click().then(()->
# Verify that toast appears and contains an error
toastMessage = $('.toast-message')
expect(toast.isDisplayed()).toBe(true)
expect(toastMessage.getText()).toBe("Invalid password")
)
)
)
The relevant markup (jade) is below:
.toaster(ng-show="messages.length")
.toast-message(ng-repeat="message in messages") {{message.body}}
The problem is the toastMessage test is failing (it can't find the element). It seems to be waiting for the toast to disappear and then running the test.
I've also tried putting the toastMessage test outside the then() callback (I think this is pretty much redundant anyway), but I get the exact same behaviour.
My best guess is that protractor sees that there's a $timeout running, and waits for it to finish before running the next test (ref protractor control flow). How would I get around this and make sure the test runs during the timeout?
Update:
Following the suggestion below, I used browser.wait() to wait for the toast to be visible, then tried to run the test when the promise resolved. It didn't work.
console.log "clicking button"
loginButton.click()
browser.wait((()-> toast.isDisplayed()),20000, "never visible").then(()->
console.log "looking for message"
toastMessage = $('.toaster')
expect(toastMessage.getText()).toBe("Invalid password")
)
The console.log statements let me see what's going on. This is the series of events, the [] are what I see happening in the browser.
clicking button
[toast appears]
[5 sec pass]
[toast disappears]
looking for message
[test fails]
For added clarity on what is going on with the toaster: I have a service which essentially holds an array of messages. The toast directive is always on the page (template is the jade above), and watches the messages in the toast service. If there is a new message, it runs the following code:
scope.messages.push(newMessage)
# set a timeout to remove it afterwards.
$timeout(
()->
scope.messages.splice(0,1)
,
5000
)
This pushes the message into the messages array on the scope for 5 seconds, which is what makes the toast appear (via ng-show="messages.length").
Why is protractor waiting for the toast's $timeout to expire before moving on to the tests?
I hacked around this using the below code block. I had a notification bar from a 3rd party node package (ng-notifications-bar) that used $timeout instead of $interval, but needed to expect that the error text was a certain value. I put used a short sleep() to allow the notification bar animation to appear, switched ignoreSynchronization to true so Protractor wouldn't wait for the $timeout to end, set my expect(), and switched the ignoreSynchronization back to false so Protractor can continue the test within regular AngularJS cadence. I know the sleeps aren't ideal, but they are very short.
browser.sleep(500);
browser.ignoreSynchronization = true;
expect(page.notification.getText()).toContain('The card was declined.');
browser.sleep(500);
browser.ignoreSynchronization = false;
It turns out that this is known behaviour for protractor. I think it should be a bug, but at the moment the issue is closed.
The workaround is to use $interval instead of $timeout, setting the third argument to 1 so it only gets called once.
you should wait for your toast displayed then do other steps
browser.wait(function() {
return $('.toaster').isDisplayed();
}, 20000);
In case anyone is still interested, this code works for me with no hacks to $timeout or $interval or Toast. The idea is to use the promises of click() and wait() to turn on and off synchronization. Click whatever to get to the page with the toast message, and immediately turn off sync, wait for the toast message, then dismiss it and then turn back on sync (INSIDE the promise).
element(by.id('createFoo')).click().then(function () {
browser.wait(EC.stalenessOf(element(by.id('createFoo'))), TIMEOUT);
browser.ignoreSynchronization = true;
browser.wait(EC.visibilityOf(element(by.id('toastClose'))), TIMEOUT).then(function () {
element(by.id('toastClose')).click();
browser.ignoreSynchronization = false;
})
});
I hope this can help who has some trouble with protractor, jasmine, angular and ngToast.
I create a CommonPage to handle Toast in every pages without duplicate code.
For example:
var CommonPage = require('./pages/common-page');
var commonPage = new CommonPage();
decribe('Test toast', function(){
it('should add new product', function () {
browser.setLocation("/products/new").then(function () {
element(by.model("product.name")).sendKeys("Some name");
var btnSave = element(by.css("div.head a.btn-save"));
browser.wait(EC.elementToBeClickable(btnSave, 5000));
btnSave.click().then(function () {
// this function use a callback to notify
// me when Toast appears
commonPage.successAlert(function (toast) {
expect(toast.isDisplayed()).toBe(true);
});
});
});
})
});
And this is my CommonPage:
var _toastAlert = function (type, cb) {
var toast = null;
switch (type) {
case "success":
toast = $('ul.ng-toast__list div.alert-success');
break;
case "danger":
toast = $('ul.ng-toast__list div.alert-danger');
break;
}
if (!toast) {
throw new Error("Unable to determine the correct toast's type");
}
browser.ignoreSynchronization = true;
browser.sleep(500);
browser.wait(EC.presenceOf(toast), 10000).then(function () {
cb(toast);
toast.click();
browser.ignoreSynchronization = false;
})
}
var CommonPage = function () {
this.successAlert = function (cb) {
_toastAlert("success", cb);
};
this.dangerAlert = function(cb) {
_toastAlert("danger", cb);
}
}
module.exports = CommonPage;
Chris-Traynor's answer worked for me but i've got an update.
ignoreSynchronization is now deprecated.
For those using angular and protractor to test this, the below works nicely for me.
$(locators.button).click();
await browser.waitForAngularEnabled(false);
const isDisplayed = await $(locators.notification).isPresent();
await browser.waitForAngularEnabled(true);
expect(isDisplayed).toEqual(true);
I've simplified this to make it easier to see, I would normally place this inside a method to make the locators dynamic.