testing non-angular app with protractor and webdriver fundamentals - javascript

I am writing e2e tests for a non-angular app using protractor and while there is quite a bit of information on how to accomplish this, it seems there are many different ways and so I have some fundamental questions.
When testing non-angular sites some folks say to just use webdriver calls, 'browser.driver.get()' for instance so that protractor does not expect angular to finish loading on a page etc. Other responses say that you can set browser.ignoreSynchronization = true, and make calls to browser.get() with no problem (this seems like the ideal solution so you can rely on one api call if dealing with angular and non-angular). Are these two methods equivalent and if not please explain differences.
Does protractor still respect promises when dealing with webdriver or browser.ignoreSynchronization = true? For instance if I make a 'get' call, will protractor wait before executing next step until that 'get' request is fulfilled?
Related to #2, Do I have to use promise chaining when dealing with webdriver/non-angular app to ensure user interactions are executed in order? For example if I use sendKeys() to enter a name and then click() to submit info, do I have to nest the click() inside of sendKeys().then?
How exactly does webdriver know all elements of a page are ready after a get request (every solution I see involves using a timeout or relying checking if an element exists first)? What if that page has embedded apps (like a google map for instance) and I want to simulate a user clicking on 'view larger map' for my e2e test?

No, they are not equivalent. browser.get() is a wrapper on top of browser.driver.get(). It will throw an error if it does not find angular library on page load. So, use browser.driver.get() for non angular apps.
No, it does not respect promises and will not wait.
No, webdriver manages that using promise library and control flow.

Related

External promises don't trigger Angular2 change detection (UPDATE: they do)

I'm working on an electron application, whose client side is written in Angular2.
I'm having the classic problem that a lot of people have encountered (eg. here and here), namely that I'm updating my component's data, but the view doesn't get updated because Angular doesn't know that the data has changed. The solutions people suggest (see above and also here) is to manually run change detection, either on the whole component tree, or part of it (similar to Angular 1's $rootScope.$digest and $scope.$digest).
However, I'd like to avoid wrapping all my data changes in zone.run() calls, as it slightly defeats the purpose of using Angular.
I'm pretty sure I know why this happens: I'm creating Bluebird Promises outside of Angular, in electron code (ie, the main process), and these aren't Zone aware, so they don't notify Angular of the changes.
I don't know how to solve it, though. What can I do to create Zone aware promises in my electron code, to avoid having to manually run change detection all the time? Can I convert my Bluebird Promises to Zone aware Promises somehow?
EDIT: I think I was wrong, Bluebird promises aren't Zone aware even if created within angular. They're just not Zone aware in general.
EDIT 2: I was completely wrong in the previous edit. Bluebird promises work just fine with zones. However, in the context of an electron application, creating a promise in the main electron process and using it in the renderer process (where Angular lives), doesn't work, as the returned promise isn't zone-aware. Creating the promise from Angular code worked.
Promises don't make Angular run change detection with ChangeDetectionStrategy.OnPush except when you use the async pipe ... | async.
When code is initialized outside Angular2 it runs outside Angulars zone. What you can do is to move initialization inside your Angular2 code or use ´zone.run(...)to move execution inside Angulars zone. There is nothing bad aboutzone.run(...)`.
If the code executed only changes local properties of a component you can use ChangeDetectorRef.detectChanges() to run change detection for this component.
If the code causes changes in other components (like for example this.router.navigate(...), then detectChanges() is not sufficient. For this case zone.run() should be used.
setTimeout(...) triggers change detection for the whole application and should be avoided as a way to invoke change detection manually.
As explained in EDIT #2, the problem was that creating a promise in the main electron process made the promise non-zone-aware. Creating the promise from Angular solved it.

When do I have to wait for a promise in Protractor?

I know there is similar questions on here about this, but I cannot make sense of them for the life of me.
Here's an example, where I need to click a button and check the url.
My initial thought is I would write it as
element(by.id('button')).click();
expect(browser.getCurrentUrl()).toContain('asyncisconfusing');
I know the expect handles its promise but what about the .click? Shouldn't I have to write it like this?
element(by.id('button')).click().then(() => {
expect(browser.getCurrentUrl()).toContain('asyncisconfusing')
})
Or is protractor/webdriver auto-magically doing this?
In theory, since Protractor maintains a queue of promises via Control Flow and works in sync with an AngularJS application under test, you should not resolve promises explicitly unless you need a real value for further processing. In other words, this should be the prefferred form:
element(by.id('button')).click();
expect(browser.getCurrentUrl()).toContain('asyncisconfusing');
In practice though, explicitly resolving click() promises, or adding explicit waits via browser.wait() helps to deal with occasional and random timing issues.
http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/promise.html
The first section talks about how the control flow is used to manage promises without having to chain together every single command.

How to wait for a event to be processed in a protractor end to end test?

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

Should I explicitly wait for element to be visible in Protractor test?

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)

How do I tell Selenium that an Angular controller has "loaded"

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.

Categories