7

Edit: Note that I found the root of my problem after help from @ernst-zwingli, so if you have this same error one of his noted fixes might help you out. My problem is a known issue with Protractor itself, if you think this may be you, I've expanded on my steps to pinpoint the root of the problem after my original question.


I'm trying to use Protractor in an Angular2 (just Angular) application built using angular-cli.

My problem: Elements on an Angular app page are not being found when browser.waitForAngularEnabledis at it's default setting of true (as in 'I believe I am on an angular page and would like for Protractor to do it's magic'). They are being found just fine if I set browser.waitForAngularEnabledto false (as in 'I am not on an angular page and would like to handle this myself, take a seat Protractor'). How do I track down what's causing this on my definitely Angular pages?

I have a product with a non-Angular Auth0 login page that gates access to the rest of the product that is written in Angular (Angular 4.3.2 to be exact). I have successfully traversed logging in on the non-Angular login page. I flipped the waitForAngularEnabled switched to false to facilitate the non-Angular login. I turned it back to true at the point where I expected my initial landing page (Angular) to be loaded, after clicking the submit button. Code is as follows:

        browser.waitForAngularEnabled(false);
        browser.driver.get('https://dashboard.net/projects');
        browser.driver.sleep(10000);
        browser.driver.findElement(By.css("[type='email']"));
        browser.driver.findElement(By.css("[type='email']")).sendKeys("email@example.com");
        browser.driver.findElement(By.css(".auth0-label-submit")).click();

        browser.driver.findElement(By.id("passwordInput")).sendKeys("password");
        browser.driver.findElement(By.id("submitButton")).click();
        browser.driver.sleep(5000);  // needed if not waiting for Angular
        //browser.waitForAngularEnabled(true);  // Back to Protractor land we go
        let elementToFind = element(by.className("header-text"));
        elementToFind.isDisplayed().then(function() {grabTheDarnLocalStorage()});
        expect(elementToFind.isDisplayed()).toBeTruthy();

If I uncomment the browser.waitForAngularEnabled(true); line to state that I'm back in Angular code I get the error trace as follows:

Failed: Timed out waiting for asynchronous Angular tasks to finish after 30 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(css selector, .header-text)
ScriptTimeoutError: asynchronous script timeout: result was not received in 30 seconds
  (Session info: chrome=61.0.3163.100)
  (Driver info: chromedriver=2.32.498550 (9dec58e66c31bcc53a9ce3c7226f0c1c5810906a),platform=Windows NT 10.0.14393 x86_64)
    at WebDriverError (C:\Users\c-shouston\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\error.js:27:5)
    at ScriptTimeoutError (C:\Users\c-shouston\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\error.js:203:5)
    at Object.checkLegacyResponse (C:\Users\c-shouston\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\error.js:505:15)
    at parseHttpResponse (C:\Users\c-shouston\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\http.js:509:13)
    at doSend.then.response (C:\Users\c-shouston\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\http.js:440:13)
    at process._tickCallback (internal/process/next_tick.js:109:7)
From: Task: Protractor.waitForAngular() - Locator: By(css selector, .header-text)

I've referenced the FAQ: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular I have my devs stating that they don't use $timeout (they use (Edit: NOT $interval) Observable Interval thank you very much) and they're not sure about $http.

I found this solution about the canonical way to debug protractor Angular sync issue issue: Canonical way to debug Protractor-to-Angular sync issues but I'm not sure the solution works without access to modifying the dev code to run the programmatic tracker. (Edit: I never did figure out how to get this to work)

I also found this about a long timeout you add before each test, but I feel this is unnecessary overhead that makes your overall test execution take longer than it should without understanding the root cause of the problem: https://stackoverflow.com/a/37217167/2718402 (Edit: yeah, this is a bad idea and adds unnecessary time to your tests, please don't do this)

The frustrating bit is that this seems to be a common occurrence and there doesn't seem to be a streamlined documentation on how to deal with it. Logging in with a non-Angular page only to transition to an Angular page. Angular pages not being picked up properly by Protractor. All of the examples I find online are bits of code that I don't have a reference for where they should be at in my overall test framework. I would kill for a full example of someone testing a non-Angular login that transitions to a fully Angular website, with a setup config and real world test cases. (Edit: This is still true, but I can't make one myself as my application is in a bad grey area, note my RCA below for more details.)

I just want the ability to do my login and then successfully transition over to my Angular pages and be able to rely on Protractor to work with my Angular pages. I need to know what to look for that may be a long running asynchronous process (What specifically can I check for in the Chrome dev tools?). I would love to understand what Protractor needs as defaults in order to successfully work with the Angular parts of my app/website (Is there something beyond the presence of <app-root _nghost-c0="" ng-version="4.3.2"> in the HTML?). Before this job I worked in Java, so all of this asynchronicity and Angular is new to me, so I know I'm missing the known things that a seasoned Javascript dev is aware of.


My Solution/Root Cause Analysis

Starting down the list suggested by @ernst-zwingli:

for Angular(2) Check if the object window.getAllAngularRootElements returns at least one value.

It returned at least one value, so I moved on.

useAllAngular2AppRoots: true,

I tried this and still ran into the async timeout.

And if $interval or other long lasting asynchronous tasks are used, there can be issues, because of the zones

Previously @ernst-zwingli also mentioned looking at the testability method, except it was the old way. Through research and testing I found the window object also has a getAllAngularTestabilities method. This led down an interesting rabbit hole. An example output from the Chrome console (put window.getAllAngularTestabilities() in the Chrome console window, look at the resulting list) is as follows:

t:
  _callbacks:...,
  _didWork:true,
  _isZoneStable: true (this looks promising, but why isn't Protractor working then?!?)
  _ngZone:
    hasPendingMacrotasks: true,
    hasPendingMicrotasks: false,
    isStable: true

I would think isZoneStable would be enough, but apparently not so for Protractor. Then looking at Macrotasks being true, I had to look up what the heck a Macrotask was: What does hasPendingMacrotasks and hasPendingMicrotasks check for?.

A macrotask can be:

i.e. setTimeout, setInterval, setImmediate

Thus @ernst-zwingli's note about interval's causing problems in the zones was remembered and something finally clicked.
First github issue, about zone instability

Another github issue complaining about the necessity of using browser.driver to get things done along with browser.waitForAngularEnabled. Apparently this is expected behavior, it led me to issue #3349

Issue #3349 - The actual root cause of my issue. My developers do not actively jump in and out of zones around observables. Even though these observables only have one subscriber. Since they live in the angular zone at this time, they are a long running "Macrotask" that Protractor waits infinitely on.

I can't rewrite the code with these wrappers as I am not currently versed enough in Angular to do it safely and we are currently hurtling toward a November deadline. I think I'll have to deal with using browser.driver for the time being and hope I can't get it fixed later. Hopefully my RCA was helpful for you.

S.Huston
  • 254
  • 6
  • 18

2 Answers2

7

In the following I list a set of potential causes and possibilities to fix/resolve them.

How does AngularJS and Angular(2) Work / What can I check in the Browser Dev Mode

I can't explain it as well as Andrey Agibalov in his Blog here, so check it out (also for developers).

Basically, the objects required by Protractor you can check in your Chrome Dev.

for AngularJS Check if the object window.angular is properly defined, i.e. lookup window.angular.version and also try window.angular.getTestability of your Root element

for Angular(2) Check if the object window.getAllAngularRootElements returns at least one value.

Root Element (AngularJS)

Potentially your Angular App is somewhere wrapped within the Body as something like <div ng-app="my-app">. In that case, you must adjust your rootElement: body inside config.ts. Check this answer for details.

Angular(2)

If you're using Angular (aka Angular2), then there are ngZone's introduced. In this case your config.js should additionally contain this:

exports.config = {
    framework: 'jasmine',
    seleniumAddress: 'http://localhost:4444/wd/hub',
    specs: ['spec.js'],
    useAllAngular2AppRoots: true,
    // rootElement: 'root-element'
};

check in your browser for window.getAllAngularRootElements as the additional line in conf.js is about this.

If you can, maybe use the advantage of multiple zones possible. Create a 2nd zone, configure rootElement: 'root-element' to only focus on one zone and then move some asynchronous tasks into the other zone until you found, which task(s) lead to timeout. Keep those tasks (if possible) in the separate zone, so Protractor ignores those tasks.

And if $interval or other long lasting asynchronous tasks are used, there can be issues, because of the zones. Repeatedly or long lasting tasks should be started outside the zone and then be moved into the zone as else Protractor could run into timeouts. There is a workaround for developers to apply, in order to avoid these problems for Protractor. read all about it here

browser.driver. - side remark

browser.driver.get() works as if ignoreSynchronization = true, since you directly assign the Browser Driver and you kind of bypass the synchronization logic of Protractor. Read more about it in this answer here.

Hope I could give you some more input and you can solve your issue. Please let me know the results.

Ernst Zwingli
  • 1,402
  • 1
  • 8
  • 24
  • This is amazing. There's a lot here that I didn't know about/know how to look up. I'll answer them in order as I go: - window.angular is 'undefined' in my browser console when I'm on my angular pages (we also use angular-cli, not sure how that might affect the answer) - I don't have an ng-app in the html of my product, which my dev believes is replaced by app-root since we use angular-cli - As stated in the prev point, angular-cli, so I tried useAllAngular2AppRoots, got Error: Timeout - Async callback [not in time] specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. – S.Huston Oct 06 '17 at 18:25
  • I edited my Answer to better distinct between AngularJS and Angular(2). As you're using 'Cli', it's almost certain, that you work with Angular(2). As one more possibility you could use some config-help-tool like [Cliptor.js](https://www.npmjs.com/package/cliptor) to cross-check your conf.js towards that result. – Ernst Zwingli Oct 07 '17 at 12:56
  • I didn't explicitly mention it before. Please re-check your issue based on my edits. You should be able to solve the problem now based on the Angular(2) information in my answer. – Ernst Zwingli Oct 07 '17 at 13:50
  • I appreciate the update! I'm investigating your suggestions. Just as a note: the function to get testability (at least with the version of Angular2 I'm on) is window.getAllAngularTestabilities(). This returns per Angular root (of which I have one, det by window.getAllAngularRootElements()), and the one I have has a property of `_ngZone:isStable = true`on the page I'm running into trouble with. As far as I can tell there is no window.getAllAngularRootElements()[0].getTestability method. Also, long lasting asynchronous tasks would show up as unending network calls in console, correct? – S.Huston Oct 09 '17 at 13:28
  • 1
    I think your answer is the best for someone trying to debug what went wrong, though the testability method for Angular2 is out of date, as I stated before. If you could edit that I'll mark this as the accepted answer. I'm going to add what ended up being the problem for clarity's sake. Thanks for the help! – S.Huston Oct 09 '17 at 18:09
  • @S.Huston I removed `getTestability` as it doesn't add to clarity in debugging the cause (getAllAngularRootElements is sufficient). Also added an idea of using two zones and letting Protractor ignore one to evaluate tasks, that are potentially an issue. – Ernst Zwingli Oct 09 '17 at 18:21
0

Could you please set

    browser.ignoreSynchronization = true;

and try

  • browser.waitForAngularEnabled(false); is the canonical way to do it now. It's the equivalent line to what you wrote. Thanks though! – S.Huston Oct 06 '17 at 13:35