0

The Problem

I have a simple 'New Booking' button I wish to click. As shown below

<button _ngcontent-c9="" class="mat-menu-item" mat-menu-item="" role="menuitem" routerlinkactive="menu-highlight-item-left" tabindex="0" ng-reflect-router-link="/booking,create" ng-reflect-router-link-active="menu-highlight-item-left" aria-disabled="false">
    New Booking
<div class="mat-menu-ripple mat-ripple" matripple="" ng-reflect-disabled="false" ng-reflect-trigger="[object HTMLButtonElement]">
</div>
</button>

Something to Note

This is a Material 2 Angular 5 Application, so every page uses and functions off of Angular.

In the past, the code I will provide further down had worked 100% every single time I ran the test without fail, which leads me to believe something else in the source code may be what causing this issue. However no additional load/spinners or popups are in the way

It has absolutely no unique attributes whatsoever, no surrounding divs with IDs to attach to or any other way of grabbing the element. However using my knowledge of XPath, despite the problem I managed to find a unique locator which I believe works, as shown below.

var btnNewBooking = element(by.xpath("//*[text()='New Booking']"));

I know for a fact this should grab the element, because when I query the xpath through the ChromeDevTools it highlights the element.

The Code that originally worked

var btnNewBooking = element(by.xpath("//*[text()='New Booking']"));
btnNewBooking.click();

The Exception produced from the ProtractorJS Console

Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds. This may be because the current page is not an Angular application. Please see the FAQ for more details: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular
    While waiting for element with locator - Locator: By(xpath, //*[text()='New Booking'])

What i've tried so far?

I've tried using Protractors built in Expected Conditions from the API Library they have provided this includes all of these:

1) Waiting Until the Presence of the element

var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to be present on the dom.
browser.wait(EC.presenceOf($('#abc')), 5000);

2) Waiting Until the Visibility of the element

EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to be visible on the dom.
browser.wait(EC.visibilityOf($('#abc')), 5000);

3) Waiting until the clickability of the element

var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to be clickable.
browser.wait(EC.elementToBeClickable($('#abc')), 5000);

I've also tried using Angular's own waits such as:

browser.WaitForAngular();

and also

browser.WaitForAngularEnabled();
demouser123
  • 4,108
  • 9
  • 50
  • 82
Taran
  • 491
  • 2
  • 8
  • 22
  • browser.waitForAngularEnabled(false) did you tried this? – Chellappan வ Aug 08 '18 at 14:28
  • It is not my code, but I have grown to like the click() method at https://github.com/hetznercloud/protractor-test-helper which builds in waits and retries (As well as letting you specify the time out, if you wish). – Jeremy Kahan Aug 09 '18 at 05:09
  • @Chellappan this is brilliant and it works thank you, However it leads to the other angular elements on the proceeding pages to have errors with attempting to find/click them since it's no longer using any of the pre-built angular waits for angular page loading. I tried to combat this by resetting to browser.waitForAngularEnabled(true); but this doesn't bring it back to it's original state in handling angular page rendering. Theres already a bug raised about this last year, but it came to no solution yet https://stackoverflow.com/questions/42648077/how-does-waitforangularenabled-work – Taran Aug 09 '18 at 07:56
  • Your results on what @Chellappan suggested tells us the locator has not become busted over time. That is useful. I understand why you don't want to mess with the waitForAngularEnabled setting. It seems that then exploring .findElement might be the way to go. Or the whole async..await approach as suggested below. – Jeremy Kahan Aug 09 '18 at 13:43

2 Answers2

0

The click() method returns a promise, if you need some documentation on that you can surely read the following: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

The Protractor/WebDriverJS "Control Flow" takes care of settling these promises. However, my team has been struggling with the synchronization not being perfect.

A solution to this issue is the actually use EC to await for the element and then settle the promise of the click by chaining

.then()

Check out this stack overflow answer: Protractor, when should I use then() after a click()

Even better?

We use the new async functions to await promises in our protractor tests. This has enabled us to refactor much of the logic and ensure a proper controlled flow. This link describes well in the Fork in the road section why a "serial" code (executes one step at a time) is the outcome of using async/await: https://ponyfoo.com/articles/understanding-javascript-async-await

Achooo
  • 109
  • 6
  • This is a brilliant example thank you, however I have given the chain linking of promises a go and it availed to the same timeout result, see the improved code below... expect(btnNewBooking.isPresent()).toBe(false); element(btnBookings.click().then(function () { expect(btnNewBooking.isPresent()).toBe(true); })); – Taran Aug 09 '18 at 07:51
  • btnNewBooking.isPresent() is also a promise, try resolving that, when writing our tests I found this helpful: https://stackoverflow.com/questions/34811569/what-does-ispresent-in-protractor-testing-actually-do https://stackoverflow.com/questions/27947094/how-to-create-a-condition-in-protractor-for-when-an-element-exists-or-not – Achooo Aug 09 '18 at 13:40
  • We have a similar function using async/await, makes it all much cleaner. Your code could then use the expect and look like this: `expect(await btnNewBooking.isPresent()).toBe(true); ...` – Achooo Aug 09 '18 at 13:45
0

You should try disabling Protractor to wait for being synchronous with Angular. This can be done by browser.ignoreSynchronisation. Set it to truein your conf.js file.

demouser123
  • 4,108
  • 9
  • 50
  • 82
  • 1
    The authors at https://github.com/angular/protractor/blob/master/lib/browser.ts say "This property is deprecated - please use waitForAngularEnabled instead." – Jeremy Kahan Aug 09 '18 at 05:02
  • Ah. I didn't knew that. – demouser123 Aug 09 '18 at 06:59
  • 1
    It's also an Angular Application, so this isn't something that I should do due to the number of HTTPS requests and angular page renders – Taran Aug 09 '18 at 09:22