I use protractor to test an angular app and I have a weird problem. Occasionally or since recently, protractor has been stalling/slowing down considerably.
I narrowed down to the issue and can see that it's taking a long time for a simple someElement.getText().then(...) to resolve; so the .then(...) part never executes; however, putting an allScriptTimeOut: 500 000 waits untils that promise eventually resolves which takes about 6mins to resolve (very inconvenient!).
Again, waiting with allScriptTimeOut: 500 000 will eventually work but takes too long.
Another solution to this extreme slowdown is to tell protractor hey Protractor, don't wait for Angular to finish fufilling all promises and asynchronous background tasks and just go on, don't wait for angular and this would work with browser.ignoreSynchronization = true;
However setting this boolean to true is problematic because it basically treats your whole angular app as a NON-angular app and never waits for angular and thus causing all sort of issues with Protractor. Another complication is that our app is NOT entirely angular, some pages are non-angular; anyways; here's my question:
Is there a way to hook into the controlflow queue and basically poll or query the queue by asking hey controlflow queue tell me whenever any action that was enqueued is taking longer than 11sec? and if yes, set browser.ignoreSynchronization = true; a sudo-like code would look like this:
protractor.controlflow(function(delay){
if(delay > 11 sec){
browser.ignoreSynchronization = true;
}else{
browser.ignoreSynchronization = false;
}
});
This might not make sense syntactically but i'm more interested in the concept than properly written javascript;
Thanks again hopefully that's clear enough to understand my problem
UPDATES:
I checked again and basically, Protractor is able to click on most button, but then it gets to another button which was at the bottom of the screen, but I scrolled first to bring button into view, then tried to click on it and it's just taking 6mins then it finally clicks on that button.
Why is it taking this long before it finally successfully clicks on that button?
With respect to your question about changing ignoreSynchronization within a test, this is currently not possible (up through protractor 3.3.0) because the built-in waitForAngular function does not implement that variable within the context of the control flow (browser.ts:399 would have to be wrapped in control flow so that it would recognize when the variable is being changed). Hopefully that kind of changed can be patched into a future release, but it would introduce a breaking change from the existing behavior.
Related
So I have this single page application that does a lot of computation every time the user does an action like click a button. As javascript is a not threaded, the lengthy calc blocks the UI updates and creates a bad user experience:
$('update-btn').click() {
updateDomWithAnimation();
doLenghtyCalc();
}
After reading perhaps one too many articles on how to handle this, I find that wrapping some of the function calls with window.setTimeout does help. So armed with this knowledge I have started wrapping them up and it seems to bring some responsiveness back to the browser.
However my question is, are there any adverse side effects of having too many timeout statements even if the delay is only 0 or 1? From a business logic perspective I am making sure only independent standalone functions are wrapped in setTimeout. I wanted to check from a technical viewpoint. Can any JS gurus share some insight?
P.S: I had taken a look at Web Workers, but my code is built using Jquery and depends heavily on DOM states etc so implementing web workers atm would not be possible which is why I am using timeouts
Much appreciated!
While technically it's ok to have several timeouts running it's generally advisable to not have too many.
One thing we did was to have a single timeout/setInterval each that when fired runs a set of functions that can be added or removed at anytime.
///Somewhere
var runnableFunctions = []
var runningIntervalID = window.setInterval(function() {
runnableFunctions.forEach(function(func) {
if(typeof func === 'function') {
func.call(null);
}
}, 1);
/// Elsewhere
$(domElem).on(event, function() {
runnableFucntions.push(function() {
//Do something on interval
// slice out this function if it only needs to run once.
});
});
This is just a dirty example but you get the idea where you can shove functions into an array and have them run in a single timeout/interval vs setting up many timeouts/intervals and then remembering to stop them later.
I have very simple AngularJs 1.4.8 Front-End:
When the form is filled and the OK button is pressed the new person is added to the table.
The form is in the addingPersonController and the table is in the allPersonsController, both are childs under the masterController.
When a user is added(OK clicked) the following happens:
a POST request is sent to the server in order to add the new person.
a personAddedEvent is emitted with $emit from the addingPersonController to the masterController
the masterController broadcasts an updatePersonsEvent to the allPersonsController
the allPersonsController sends a GET request to the server in order to get all the persons and update the table.
I have a protractor end to end test that exercises this case, the problem is that I can not figure out how to wait for the whole process to finish before to check the assertions and the test always fails.
The UI works when tested manually and I have tried to wait in the protractor test with:
browser.wait(function(){}, timeout);
browser.sleep(ms);
.click().then();
None of them worked.
Is there a way to wait for such a process to complete before checking the assertions?
Got some similiar issues with my e2e tests too. There are couple of work-arounds can be added. Depend on situations , mostly due to some sort of non-response timeout (lost synchronization between protractor/selenium and browser). It is like a hang of the PC, cause by lack of resource (either GPU/VRAM, RAM or CPU). And this cause the built-in "wait for angular works to be done" of protractor lost its tracking. And run the spec before it should be ran.
Here is the list I will suggest you to try:
use built-in wait for angular Quick and solve the problem most of the time. But it will have nothing to deal with the lost synchronization between protractor/selenium and browser.
browser.waitForAngular();
use ExpectedConditions with browser.wait() This is always the best because it will sure work for all case. Even outside angular environment. Bad thing about this is... it is quite long to type
browser.wait( ExpectedConditions.textToBePresentInElement( $('#someId'), "Person's Name"), 10000);
EDIT: typos and make answer more understandable
If my page is rendered using AJAX request should I execute something like
waitForElementToBeVisible('.todoListItem');
//that is my custom function that waits
//till element will be rendered
before making call:
element(by.model('todoList.todoText')).sendKeys('write first protractor test');
which sendKeys to element with CSS class .todoListItem?
If this is an AngularJS application under test, things should be handled naturally by protractor - it always works in sync with Angular. This is, though, theory.
In practice, this is not always the case - for instance, our test codebase has the browser.wait() calls here and there to make the tests flow the way we want them to work.
Note that disabling Angular animations and increasing the implicit wait timeout sometimes help too.
See also:
a custom waitReady() function to wait for element to be present and visible
protractor-flake package (rerun potentially flakey protractor tests before failing)
I've got some UI tests that are attempting to test that a click on an element makes something else appear. There's an existing check for all tests that looks to see if the DOM is Ready, however there's a small amount of time between that even firing and the app.controller() calls all completing where my test could jump in and wrongly determine that the click handler has not been added.
I can use angular.element('[ng-controller=myController]').scope() to determine if the scope is defined, however there is still a very small window where the test could run before the click handler is bound (a very, very small window).
<div ng-controller="myController">
<div ng-click="doWork()"></div>
</div>
Can anyone see a way to tell that the click handler has been added?
PS: There's an event that fires within a controller when the controller has loaded:$scope.$on('$viewContentLoaded', function(){ }); But that doesn't really help me unless I subscribe to it in the controller and flag a variable somewhere that I can check via Selenium.
PPS: A lot of these have classes that change when scope changes and they can be used to trigger the test, but many do not.
There is a specialized tool for testing AngularJS application - protractor. It is basically a wrapper around WebDriverJS - selenium javascript webdriver.
The main benefit of using protractor is that it knows when Angular is settled down and ready. It makes your tests flow in a natural way without having to use Explicit Waits:
You no longer need to add waits and sleeps to your test. Protractor
can automatically execute the next step in your test the moment the
webpage finishes pending tasks, so you don’t have to worry about
waiting for your test and webpage to sync.
It also provides several unique AngularJS-specific locators, like by.model, by.binding etc. And, in general, it provides a very convenient and well-designed API for end-to-end testing.
There are two issues to overcome here:
How do we know when Angular is done (with the sub issue of "what does done mean?"
How do we get that information to Selenium
Angular provides a method called "getTestability" that can be called with any element (assuming you've included it, it is optional). Usage:
angular.getTestability(angular.element('body')).whenStable(function(){/*Do things*/})
That seems to solve the first problem...
But, now what does Done mean in this case. Done means that anything that uses $browser.defer will have been executed. What does that mean? No idea, but in practice it at least verifies that there are no http requests in play when the callback is called.
Ok, now Selenium... You can ask it to execute JavaScript on the client and use the code above to set a variable. .whenStable(function(){window.someVar=true}) and then poll in the test until that variable is set.
Will this catch all cases of "Done"? Probably not, but it made my tests pass more consistently. As long as it works I'm not going to think any harder on the issue.
That said, I'm not marking this as the answer. It feels like a dirty solution.
I have been working on writing a library of code for my future projects. One of the functions I've been working on is a pause function. So far I have no problem with the errors reporting that the script is running to long even on pauses as long as 10 seconds. This is to primarily keep malicious users busy, it works well when you set a very long time. I was wondering if there are any errors that I should look out for that I might face?
Here's the code...
pause = function(a) {
var b = new Date().getTime();
e = false;
function wait() {
d=10;
for(i=0;i<d;i++) {
d++;
var c = new Date().getTime();
if(c-b>=a) {
e = true;
break;
}
if(d>1000000) {
break;
}
}
}
wait();
if(e==false) {
pause(a-(new Date().getTime()-b));
}};
You never ever want to do this sort of thing in Javascript. As you have noticed, the browser will complain about your script taking too long. Furthermore, this will take much more energy than necessary which is important for mobile devices with limited battery capacity.
Instead, use the standard setTimeout() function to run code at a later time.
Javascript already has a way to do that. Use setTimeout() and pass it an anonymous function with the continuation of your operation. The second argument is the delay in milliseconds.
Example:
setTimeout(function(){ alert('hello'); }, 2000);
As I've said in my comments so far, trying to run semi-infinite loops in javascript is never a good idea.
Here are some of the issues to watch out for:
If you don't return to the browser event loop by finishing your javascript thread of execution after some period of time, the browser will stop and prompt the user to kill the javascript in that browser window.
Your code uses a bunch of undeclared variables which makes them implicit global variables. Since they have common names, they could easily interfere with other code.
Because javascript is single threaded, while your loop is running, no other browser events for that window can run rendering the browser window essentially frozen. Users will think it is hung. Patient users might wait longer to see if it completes. Impatient users will kill the browser window.
Essentially what you're trying to do is a denial-of-service attack on a user's browser. You may not like what they're doing to your web page, but attacking them back does not seem appropriate. Much better to just block access to your page in a browser-appropriate way rather than a browser-attacking way.
Suggestions:
If what you're really trying to do is just render your page unuseful in some circumstances (you still haven't explained why you're trying to do this or what condition triggers your desire to do this), then it might be better to just make the page unuseful or informative without trying to kill the browser. You could do that by:
Just hiding everything in the DOM so the window goes blank.
Remove all DOM elements so the window goes blank.
Put a transparent layer over the whole window so that no input events can get to the page elements, but the visuals stay there. You could add a message to that window.
Replace the contents of your page with an appropriate message.