Protractor: Protractor fails when app is bootstrapped with ngUpgrade

Created on 16 May 2017  ·  51Comments  ·  Source: angular/protractor

I am working on migrating an AngularJS (1.6) app to Angular (4) and now have a hybrid application, bootstrapped with NgUpgrade. This seems to have completely broken my Protractor tests.

Forgive me if this is just something I am doing wrong, but I can't find any answer as to why this won't work.

The error I get is:

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(css selector, .toggle-summary-button)

Hybrid app changes

The application seems to run fine, and both AngularJS and Angular components are working as expected. The changes I made in the bootstrapping process are:

1 Removed ng-app from html tag:

<html lang="en" *ng-app="myapp">

2 Added an AppModules (@NgModule) etc.

3 Used NgUpgrade to bootstrap the app:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
  const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
  upgrade.bootstrap(document.body, ['myapp'], {strictDi: true});
});

Protractor tests

Based on the error above, the problem seems to be related to whatever Protractor does when it is waiting for Angular. I have a beforeEach block which loads the login page, fills in the details and logs in. Weirdly it is still opening the page and entering text into the username field, but then it fails to go any further.

I have tried, with no success:

  • adding "rootElement: 'body'" to my protractor config file
  • adding "ng12Hybrid: true" to my protractor config file - I get a message saying that it should no longer be needed as it auto detects.
  • increasing the allScriptsTimeout setting from 11000 to 60000 and it still times out.
  • turning off the waitForAngularEnabled setting. This solves the problem with the login fields, but then none of my http mocks work and the tests fail.

Most helpful comment

Hi - we are working on a change to let you configure which tasks Protractor waits for, that can help you handle the above issue. This is a broad change involving Zone.js and Angular and don't have specific ETA(will be in the order of weeks). Will update on progress here.

All 51 comments

I had similar issue that was solved by wrapping this upgrade code like that:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app.module';

angular.element(document).ready(() => {
  platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
    const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
    upgrade.bootstrap(document.body, ['myapp'], {strictDi: true});
  });
});

And in config I AM NOT USING any of those:

//ng12Hybrid: true,
//useAllAngular2AppRoots: true,
//rootElement: 'body',

Could you take a look at https://github.com/angular/protractor/issues/4234 and see if you're having the same problem? Also, in ngUpgrade $interval will now block Protractor where it didn't before (https://github.com/angular/angular/issues/16349), but I'm working on that.

@Evilweed - thanks for the suggestion, unfortunately that doesn't seem to solve it for me.

@heathkit - I found one long running $interval by searching the whole project, but even after removing it, the problem persists. I've also commented out all usages of $interval or $timeout in the whole app and it still fails with the same error. Do you know if there are any ways to debug and find out what it is that could be blocking if it is a problem with something async?

Can you post your full error? It's supposed to show what tasks are pending in the error message, but it's possible it could be missing some. For example, the error won't yet show pending tasks from the Angular side. If you have long running async tasks in the Angular part of your app, you'll need to run those outside the Angular zone to keep from blocking Protractor. (See https://github.com/angular/protractor/blob/master/docs/timeouts.md)

Thanks again for replying. The error detail is below. A few things to note:

  • My test file has a beforeAll block which is supposed to fill out the username and password fields to login. It fills the username field successfully but then times out in waitForAngular and doesn't fill in password field. This is what the first half of the error seems to be about. The second half is the actual failing test (which makes sense, because it failed to login so the element is not present).

  • The error suggests the problem may stem from an HTTP call, so maybe not $interval or $timeout.

  • I don't have much on the Angular side at the moment, it is nearly all still AngularJS. It doesn't get as far as showing the new Angular component because it doesn't manage to login (so I doubt the http call is to do with the Angular 4 component)

  Message:
    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(css selector, *[name="password"])
  Stack:
    ScriptTimeoutError: asynchronous script timeout: result was not received in 11 seconds
      (Session info: chrome=58.0.3029.110)
      (Driver info: chromedriver=2.29.461585,platform=Mac OS X 10.12.4 x86_64) (WARNING: The server did not provide any stacktrace information)
    Command duration or timeout: 11.07 seconds
    Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
    System info: host: 'Matt.lan', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.12.4', java.version: '1.8.0_111'
    Driver info: org.openqa.selenium.chrome.ChromeDriver
    Capabilities [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=false, networkConnectionEnabled=false, chrome={chromedriverVersion=2.29.461585, takesHeapSnapshot=true, pageLoadStrategy=normal, databaseEnabled=false, handlesAlerts=true, hasTouchScreen=false, version=58.0.3029.110, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true, unexpectedAlertBehaviour=}]
    Session ID: 40deafceb49b72b1c0188ad0895c1179
        at Object.checkLegacyResponse (/Users/matt/web-app/node_modules/selenium-webdriver/lib/error.js:505:15)
        at parseHttpResponse (/Users/matt/web-app/node_modules/selenium-webdriver/lib/http.js:509:13)
        at doSend.then.response (/Users/matt/web-app/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, *[name="password"])
        at thenableWebDriverProxy.schedule (/Users/matt/web-app/node_modules/selenium-webdriver/lib/webdriver.js:816:17)
        at ProtractorBrowser.executeAsyncScript_ (/Users/matt/web-app/node_modules/protractor/lib/browser.ts:608:24)
        at angularAppRoot.then (/Users/matt/web-app/node_modules/protractor/lib/browser.ts:642:23)
        at ManagedPromise.invokeCallback_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1366:14)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
        at TaskQueue.executeNext_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2953:27)
        at asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2813:27)
        at /Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:676:7
        at process._tickCallback (internal/process/next_tick.js:109:7)Error
        at ElementArrayFinder.applyAction_ (/Users/matt/web-app/node_modules/protractor/lib/element.ts:482:23)
        at ElementArrayFinder.(anonymous function).args [as sendKeys] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:96:21)
        at ElementFinder.(anonymous function).args [as sendKeys] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:873:14)
        at /Users/matt/web-app/e2e-tests/modules/authentication.js:83:38
        at ManagedPromise.invokeCallback_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1366:14)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
        at TaskQueue.executeNext_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2953:27)
        at asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2813:27)
        at /Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:676:7
        at process._tickCallback (internal/process/next_tick.js:109:7)
    From: Task: Run beforeAll in control flow
        at Object.<anonymous> (/Users/matt/web-app/node_modules/jasminewd2/index.js:94:19)
    From asynchronous test: 
    Error
        at Suite.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:28:3)
        at Object.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:7:1)
        at Module._compile (module.js:571:32)
        at Object.Module._extensions..js (module.js:580:10)
        at Module.load (module.js:488:32)
        at tryModuleLoad (module.js:447:12)
  Message:
    Failed: No element found using locator: By(css selector, .toggle-asset-summary-button)
  Stack:
    NoSuchElementError: No element found using locator: By(css selector, .toggle-asset-summary-button)
        at elementArrayFinder.getWebElements.then (/Users/matt/web-app/node_modules/protractor/lib/element.ts:851:17)
        at ManagedPromise.invokeCallback_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1366:14)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
        at TaskQueue.executeNext_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2953:27)
        at asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2813:27)
        at /Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:676:7
        at process._tickCallback (internal/process/next_tick.js:109:7)Error
        at ElementArrayFinder.applyAction_ (/Users/matt/web-app/node_modules/protractor/lib/element.ts:482:23)
        at ElementArrayFinder.(anonymous function).args [as click] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:96:21)
        at ElementFinder.(anonymous function).args [as click] (/Users/matt/web-app/node_modules/protractor/lib/element.ts:873:14)
        at Page.showAssetSummaryComponent (/Users/matt/web-app/e2e-tests/specs/home/page-object.js:289:31)
        at Object.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:63:12)
        at /Users/matt/web-app/node_modules/jasminewd2/index.js:112:25
        at new ManagedPromise (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:1067:7)
        at ControlFlow.promise (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2396:12)
        at schedulerExecute (/Users/matt/web-app/node_modules/jasminewd2/index.js:95:18)
        at TaskQueue.execute_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2970:14)
    From: Task: Run fit("should open asset summary component") in control flow
        at Object.<anonymous> (/Users/matt/web-app/node_modules/jasminewd2/index.js:94:19)
        at /Users/matt/web-app/node_modules/jasminewd2/index.js:64:48
        at ControlFlow.emit (/Users/matt/web-app/node_modules/selenium-webdriver/lib/events.js:62:21)
        at ControlFlow.shutdown_ (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2565:10)
        at shutdownTask_.MicroTask (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2490:53)
        at MicroTask.asyncRun (/Users/matt/web-app/node_modules/selenium-webdriver/lib/promise.js:2619:9)
    From asynchronous test: 
    Error
        at Suite.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:62:5)
        at Suite.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:60:3)
        at Object.<anonymous> (/Users/matt/web-app/e2e-tests/specs/home/asset.spec.js:7:1)

Exactly the same - with a fresh new 'quickstart' angular4 config and a very basic protractor test. Everything is working well on non hybrid mode.

Failed: Timed out waiting for asynchronous Angular tasks to finish after 50 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, #loginModal h4)

I am still seeing this issue.
Using: @angular/upgrade 4.2.2
which contains the fix from @heathkit (https://github.com/angular/angular/pull/16836) to get the Testability api stable, but it still doesn't seem to work

If someone could share a repo that shows the problem, I'll look into it further.

What I've discovered so far

After some digging I've discovered one of the causes of the timeout I'm seeing (or one of them, I suspect it's not the only one). It turns out that one of the dependencies I'm using is calling window.setTimeout() which is preventing Protractor from syncing (in my case, it's
angular-moment.

This ran fine when my app was running just AngularJS but causes a timeout issue when bootstrapped as a hybrid app (presumably because of the way it is run in zones).

Example Repo

I've created an example repo at https://github.com/mattwilson1024/angular-hybrid. It's a hybrid (NgUpgrade) app with the following parts:

  • The outermost page (the CharactersPage) is provided by AngularJS (1.6).
  • The page contains CharacterListComponent, which is a downgraded Angular (4) component.
  • The CharacterListComponent uses another Angular 4 component, CharacterDetail component.

The master branch contains the working app and a single protractor tests which passes no problem.

The angular-moment branch adds a dependency on the angular-moment library and uses its am-time-ago directive to add a label to the page showing how many years ago it is since the first episode. The app itself runs fine, but the protractor test now fails to sync.

Timeouts

To confirm this is happening I tried adding various different kinds of timeout to the AngularJS controller, with the following results. This confirmed that $interval works, but $timeout, setInterval or setTimeout do not.

function sayHello() { console.log('Hello, world'); }

$interval(sayHello, 30000); // Succeeds (with Angular 4.2.0 and later - see https://github.com/angular/angular/pull/16836)
$timeout(sayHello, 30000); // Fails - Timed out waiting for asynchronous Angular tasks to finish after 11 seconds
setInterval(sayHello, 30000); // Fails - Timed out waiting for asynchronous Angular tasks to finish after 11 seconds
setTimeout(sayHello, 300000); // Fails - Timed out waiting for asynchronous Angular tasks to finish after 11 seconds

Next Steps

Having managed to track down what's happening I'm still not sure what the solution is. I understand that it may be necessary to run certain pieces of code "outside the Angular zone", though I am not sure how to do this from AngularJS segments where I don't have a TypeScript class in which to inject the zone etc.

Even then, if the problem code is happening inside a third party library (as is the case in my example with angular-moment) - what would be the approach for working around this problem to get the tests working as they did before upgrading to a hybrid setup?

Zones are set on the window, so you don't need to inject NgZone. You can run outside the Angular zone in AngularJS like this

if (Zone && Zone.current.name === 'angular') {
  let angularZone = Zone.current;
  Zone.parent.run(() => {
    $timeout(() => {
       // Stuff here is run outside the Angular zone and will not be change detected
       angularZone.run(() => {
         // Inside the zone and will be change detected.
       });
    }, 500);
  });
}

You are correct that any use of setTimeout or setInterval, even in third party code, can block Protractor stability and cause timeouts. We're adding task tracking to Angular and AngularJS that should make it easier to debug these problems by showing you where the async task that Protractor timed out on came from. Realistically, it'll be a couple months before we can get that into Protractor.

As far as angular-moment goes, your best option might be to submit a PR to them making it zone-aware as shown above. Otherwise, you can selectively turn off waitForAngular and use browser.wait and ExpectedConditions instead. We're working on a more flexible waitForAngular API that will let you control which tasks you wait for, but that's still a ways off.

In our situation, tests can be written the generic (without Angular sync) way (browser.ignoreSynchronization = true using ECs, etc.), but fail when setting browser.ignoreSynchronization = false; with "Error while waiting for Protractor to sync with the page: "Cannot read property '$$testability' of undefined"

This basically nullifies our entire E2E automated test suite.

Using Protractor 5.1.2, if I set ng12Hybrid: true I get the message:

You have set ng12Hybrid. As of Protractor 4.1.0, Protractor can automatically infer if you are using an ngUpgrade app (as long as ng1 is loaded before you call platformBrowserDynamic()), and this flag is no longer needed for most users

So obviously the angular.io documentation is out of date here: https://angular.io/guide/upgrade#e2e-tests

Famous last words from self same document:

E2E tests aren't really that concerned with the internal structure of the application components. That also means that, although you modify the project quite a bit during the upgrade, the E2E test suite should keep passing with just minor modifications. You didn't change how the application behaves from the user's point of view.

In light of that statement on angular.io:

@heathkit Thanks for the tip about running inside and outside of zones, but to be honest we are looking for a solution that runs with our existing AngularJS code, as is. We have an extensive Protractor suite which relies on implicit AngularJS synchronization. Our goal is to migrate our AngularJS to Angular using ngUpgrade, but this issue with not being able to run our existing Protractor suite in Hybrid upgrade mode has thwarted our initiative. A sensible fix would be most welcome right about now.

If we're doing something fundamentally wrong, we'd sure like to know what.

Facing the same issue! :0

Seeing the same thing here: AngularJS 1.6 -> Angular 4.2.4

Anybody know any sort of workaround, however big it may be? Without reverting back to 1.6 that is.

@quinw68 - @heathkit suggests a method of running the code outside of the Angular 'Zone' in his previous response in this thread. I think this means changes to the source code of your app to make it work, and may even depend on third party libraries adding support. Please correct me if I'm wrong.

Another 'work-around' I'm aware of is to treat your existing app like it's not an AngularJS app anymore. This means that you will have to change your Protractor tests so that they don't inherently wait for Angular anymore, but explicitly check for the presence of elements on the page before querying them for their state. Depending on the size of your app, this could be a daunting task.

Hope that helps.

As far as I understand the issue, this is not ngUpgrade-specific, but rather Angular specific. I mean, even in a pure Angular (2+) app, having a long setTimeout (either directly or via a 3rd-party library) will cause protractor to time out.

@heathkit, can you confirm that or did I miss something? Is ngUpgrade throwing Protractor off or making things worse in some way?

@gkalpak You're right that this is more of an Angular-specific issue, but people are encountering it when they use ngUpgrade with a large AngularJS app.

In AngularJS, Protractor would only wait for async tasks started with $browser.defer() - basically just $timeout and $http calls in the application code. Once someone starts using ngUpgrade, their existing Protractor tests will start timing out due to long setTimeout calls (potentially from third-party libraries) where they didn't before. So mostly, the issue is that ngUpgrade is working as intended, and now people's AngularJS app and Protractor tests are suddenly subject to Angular (2+) semantics.

So, how would one solve this for a pure Angular app? What would one do if a third party Angular library was setting a long setTimeout, causing Protractor to time out?

@vikerman I hear you are going to help get a new team member up and running on this one pretty soon, so assigning to you until they're aboard.

We don't really have a good answer for the case where a third party app is setting a long timeout. At the moment, all you could do is turn off waitForAngular and rely on ExpectedConditions, but that's not ideal. I am working on adding a feature to Protractor that will give people more control over which macrotasks their tests wait for, but that's still a ways off.

@heathkit @vikerman Any update on this one please?

The project I work on had this problem before but they wrapped continuous polling function to run outside of the angular zone so that it didnt affect browser.waitForAngular();

First thank you to the Protractor team/contributors. Protractor has been the bedrock for my app to have very few production issues over dozens of releases over 3 years, over 200 services, directives, components, controllers with about 400 e2e Protractor tests.

This is a good reminder how much I rely on Protractor but this issue has stopped my Angular upgrade in its tracks after having dedicated 2 weeks committed to just bootstrapping ngUpgrade and the ancillary needs. That's done but now the e2e tests can't be run and deployment is stopped without a visible path forward (yet).

Thanks to the great info from @mattwilson1024, I've made sure $timeout, setTimeout, setInterval are not used in our app.

But, I've found that angular-ui-bootstrap (v.2.5.6) and ui-select (v0.19.8) do use $timeout. I use both libraries extensively. It seems a lot of others rely on them as well - angular-ui-bootstrap - 400k npm downloads/mo, ui-select 100k+/mo.

angular-ui-bootstrap created an issue in 2015 to replace $timeout with $interval specifically due to this issue but unfortunately has decided "this bug should be fixed in protractor and not here. Closing this." Also, angular-ui-bootstrap is no longer providing updates in liu of their Angular version @ng-bootstrap/ng-bootstrap which is in beta.

I'm surprised when I see significantly used angular libraries that don't ensure Protractor validity. On that, I agree with @heathkit that those libraries need to fix themselves. But, I'm hoping that Protractor can step in and mitigate it all with one fell swoop. Even a nice workaround would be golden. I'm disappointed that I'll have to back out my ngUpgrade for now until I dig out all those $timeout dependencies and, possibly, rather than just including ngGrade and starting to pick off items for upgrade when feasible, I likely have a dependency to upgrade or migrate from those 3rd party items along with adding ngUpgrade. Ugh.

I am continuing to research for my app (specific culprits causing the timeout(s), how to move forward, etc) and will report back anything that might be of use to others.

Where I've found $timeout:
angular-ui-bootstrap v2.5.6

  • UibAlertController
  • UibCarouselController
  • UibDatepickerPopupController
  • UibTypeaheadController
  • UibTooltip

ui-select

  • uiSelect
  • uiSelectMultiple
  • uiSelectSingle
  • uiSelectSort
  • uisOpenClose

angular-resource $resource injects and uses $timeout
angular-filter ($window.setTimeout)

Hi - we are working on a change to let you configure which tasks Protractor waits for, that can help you handle the above issue. This is a broad change involving Zone.js and Angular and don't have specific ETA(will be in the order of weeks). Will update on progress here.

I have the same issue when I bootstrap my [email protected] app with NgUpgrade to [email protected].
Thanks for the comments above, especially @mattwilson1024 and @tonybranfort, which helps me understand better the issue.

I've found a workaournd in my case, by adding browser.ignoreSynchronization = true/false; around the code that times out.

For example, one of my test did a simple select in a group of radio button, and expect that the correct option is selected:

it('should be able to pick option', function () {
        expect(myPage.optionSelected()).toBe(false);
        myPage.pickOption('Option 1');
        expect(myPage.anyOptionSelected()).toBe(true);
        expect(myPage.getSelectedOption()).toBe('Option 1');
});

When I try to debug this code, I found that timeout occurred at the last step. I suppose the underlying problem is a toolip from angular-ui-bootstrap, with the use $timeout: https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js

However, there are no async calls in this test. So what I do, is simply add ignoreSynchronization at the beginning and reset it in the end:

it('should be able to pick a font', function () {
        browser.ignoreSynchronization = true;  // tell protractor do not waitForAngular
        expect(myPage.optionSelected()).toBe(false);
        myPage.PickOption('Option 1');
        expect(myPage.anyOptionSelected()).toBe(true);
        expect(myPage.getSelectedOption()).toBe('Option 1');
        browser.ignoreSynchronization = false;  // reset the flag
});

I don't know if this is the recommended way or if it will make the tests more flaky. But for tests that do not require any async calls but still suffer the timeout issue, it should fix them.

Hi @vikerman - Any update on this? https://github.com/angular/protractor/issues/4290#issuecomment-337094393

@vikerman Is there any update on this?

Has anyone good advice on how to find these long running timeouts so i can get them out of my application?

From: Castaway(s?) on Island of AngularJS stranded by Protractor Issue #4290
To: @vikerman @juliemr

Ahoy! Could we get an update on this issue? Last update on Oct 16 was an ETA in the "order of weeks". Or, please close this issue so we can move on.

These are the rafts I'm currently considering:

  • Replace all angular-ui-bootstrap / ui-select. This is significant for me. It needs to be done anyway but my concern is that something else will pop up and I'll still be stuck on AngularJS after that work. Lesson learned : wrap 3rd party components with my own components.
  • waitForAngularEnabled(false) for all tests as suggested by @vladotesanovic. I'm skeptical of this for an entire app but I haven't tested it. I use waitForAngularEnabled in only one situation now and only for a very specific case.
  • Upgrade the entire app to Angular rather than AngularJS/Angular side by side incremental migration: Nope, just not an option with my resources.
  • Migrate to Vue or React instead of Angular. High on the list especially considering both can coexist with AngularJs.
  • Move to Puppeteer and ditch all Protractor tests - This is likely strategic direction.
  • OR a fix from #4290 and I can uncomment those few lines needed to start using ngUpgrade :)

Our team is in the process of upgrading our complex application and protractor tests are failing when bootstrapped with NgUpgrade. We had to revert our changes because now we cannot deal with all of the failing tests which are false positives.

Where are we with this issue? I'm worried I'm going to have to move to a different testing framework so we can keep our plans of upgrading Angular.

@tonybranfort: In the exact same boat as you... We rely heavily on ui-select and angular-ui-bootstrap- this is a major blocker for our upgrade. Looks like you've taken the lead on this issue however, please keep us updated! 😁

We are having the same problem. Can someone from protractor update us on this please?

We are halfway into upgrading our app from Angular V1 and hate to have this cause is to go back to V1 and ditch all the work done for the upgrade to Angular V5 so far.

Throwing another vote in here, we are in the process of upgrading our app to Angular and we are currently using a piecemeal application of waitForAngularEnabled(false) to get these to pass without putting the entire suite at greater risk. It's extraordinarily unideal, though, so a better solution would be greatly appreciated.

We just hit this issue as well, specifically when it comes to $interval service. Sometimes protractor is waiting for it and sometimes not - I did not find the pattern yet.

Long time edit: apparently it depends when interval gets invoked (in the zone or not)

Hi @vikerman - Any news on the changes to let you configure which tasks Protractor waits for?

Same issue here.

@vikerman is there any update or workaround?

We're running into the same issues. Would be great if there would be some progress on this issue.

Sadly, I do have the same issue. Is there any progress in sight concerning this?

We're adding task tracking to Angular and AngularJS that should make it easier to debug these problems by showing you where the async task that Protractor timed out on came from.

@heathkit Is this already done somewhere? we are having a hard time to identify those places in our codebase.

also it would be nice to have some documentation what protractor is excatly waiting for. This one seems to be outdated: https://www.protractortest.org/#/system-setup. it only mentions to replace $timeout by $interval. Nothing about setTimeout or setInterval.
some docs are here: http://www.protractortest.org/#/timeouts. but no hints how to detect them...

I think this will help you to run your tests "angular hybrid + protractor". It helped me. Check the link: https://github.com/ui-router/angular-hybrid/issues/39#issuecomment-309057814

We've made a little headway into understanding what's going on with this problem. We seem to be having the same one. It appears that upgraded angularjs watcher/digest cycle works a little differently and can enable some infinite digest cycle loops. We found 1 with a 3rd party we were using, and are investigating the next one. But basically the watcher was causing a change to the thing being watched, causing the watcher to trigger, etc. To test if you're having the same problem, in chrome just open dev tools, go to performance, record a few seconds, and see if you have NgZone calls being made a lot. If you do, then somewhere in your code you have an infinite digest cycle. Drilling into the calls can help point you in the direction of what's causing it

You can also modify local zone.js file a bit and add console logs where timeout/interval tasks gets patched to see what is calling them. Easy way to do that:

try {
  throw new Error("track");
} catch(err) {
  console.debug(err);
}

Edit: so fun to format code on mobile!

@vikerman Do you have any update on this? It would be great if you could provide your opinion on how soon this will be fixed? Thanks.

You can also modify local zone.js file a bit and add console logs where timeout/interval tasks gets patched to see what is calling them. Easy way to do that:

try {
  throw new Error("track");
} catch(err) {
  console.debug(err);
}

Edit: so fun to format code on mobile!

Where would you put that code to track the timeouts/intervals?

@maurycyg patchTimer -> patchMethod function should do the trick

This thread is disheartening to see. Is there ANY update on this?

A potential workaround to not use $timeout is to override it with $interval (for 3rd party libraries)
Code below resolved the timeout issue for me (added browser check just in case). In my case the issue was caused by angular-bootstrap components, which infinitely called $timeout with 0 delay.

app.config(function($provide) {
    function isUnderProtractor() {
        var nav = window.navigator;
        var lowercasedUserAgent = nav.userAgent;
        return !lowercasedUserAgent || lowercasedUserAgent.contains('protractor');
    }

    if (isUnderE2ETest()) {
        $provide.decorator('$timeout', ['$interval', '$delegate', function($interval, $delegate) {
            var newTimeout = function(fn, delay, invokeApply, pass) {
                if (!delay)
                    return $interval(fn, delay, 1, invokeApply, pass);
                else
                    return $delegate(fn, delay, invokeApply, pass);
            };
            newTimeout.cancel = $delegate.cancel;
            return newTimeout;
        }]);
    }
}

Starting from zone.js v0.8.9, you can choose which web API modules you want to patch as to reduce overhead introduced by the patching of these modules.
https://github.com/angular/zone.js/blob/master/MODULE.md

Add zone-flags.ts file with content below
(window as any).__Zone_disable_timers = true;

Import it in polyfills.ts before zone.js

import './zone-flags';
import 'zone.js/dist/zone';  // Included with Angular CLI.

It appeared that above solution with disabling zone timers messes up with Obserables, (ex. asyncValidators). Solution that finally worked for me is monkey-patching setTimeout.

platformBrowserDynamic().bootstrapModule(AppModule)
.then((moduleRef: NgModuleRef<AppModule>) => {
  const ngZone: NgZone = moduleRef.injector.get(NgZone);
  const originalSetTimeout = window.setTimeout;
  (window as any).setTimeout = function setTimeout(fn, ms) {
    return ngZone.runOutsideAngular(() => {
      return originalSetTimeout(() => {
        ngZone.run(fn);
      }, ms);
    });
  };
})

Perhaps the issue is not in the Protractor itself, but in the application code.

With the transition from the Angular JS which uses a digest cycle to track changes, to Hybrid App with Angular2+ witch uses zone.js to detect changes, Protractor began to fall with timeouts.

Protractor starts the next test action only when the application is stable. Stability in the case of a hybrid app means stability of Angular JS part of the application and Angular2+ part of the application. Both parts must be stable.

Angular JS stability is defined through $$testability.whenStable. The stability of Angular 2+ is determined through window.getAngularTestability(...).whenStable. You can debug Protractor code and see for yourself. To do this add the debugger; statement to the waitForAngular _\node_modules\protractor\built\clientsidescripts.js_ file;
You will notice that stability of the Angular2+ zone never occurs. For stability, all microtasks and macrotasks must be completed.

Now we understand the issue. Angular 2+ part of the application is always unstable and therefore Protractor waits indefinitely until it becomes stable and falls with timeouts. Angular JS stability in the case of a hybrid app should not have changed and does not affect the Protractor timeouts. From this we can conclude that it is necessary to fix the instability of the Angular2+ zone and then Protractor will begin to work as expected.

First attempt to fix:
Since there was little time and I wanted to fix everything all at once, I wrote code that took tasks scheduling out of the Angular2+ zone.

    $provide.decorator("$animateCss", ["$delegate", createDecorator]);
    $provide.decorator("$http", ["$delegate", createDecorator]);
    $provide.decorator("$timeout", ["$delegate", createDecorator]);
    $provide.decorator("$interval", ["$delegate", createDecorator]);
    $provide.decorator("$q", ["$delegate", createDecorator]);
    $provide.decorator("$xhrFactory", ["$delegate", createDecorator]);
    $provide.decorator("$templateRequest", ["$delegate", createDecorator]);
    $provide.decorator("$httpBackend", ["$delegate", createDecorator]);

    $provide.decorator("$animate", ["$delegate", createDecorator]);
    $provide.decorator("$rootScope", ["$delegate", createDecorator]);
    $provide.decorator("$templateCache", ["$delegate", createDecorator]);
    $provide.decorator("$browser", ["$delegate", createDecorator]);

    function runInRootZone(fn, ...args) {
        if (window.Zone.current.parent) {
            return window.Zone.current.parent.run(() => runInRootZone(fn, ...args));
        }

        return fn(...args);
    }

    function decorate(decorator, $delegate) {

        const keys = Object.keys($delegate);
        for (let i = 0; i < keys.length; i++) {

            const key = keys[i];
            if (angular.isFunction($delegate[key])) {
                decorator[key] = createDecoratorForFunctionLikeService($delegate[key]);
            } else {
                if (decorator[key] !== $delegate[key]) {
                    decorator[key] = $delegate[key];
                }
            }
        }
    }

    function createDecoratorForFunctionLikeService($delegate) {

        function decorator(...args) {
            return runInRootZone($delegate, ...args);
        }

        decorate(decorator, $delegate);

        return decorator;
    }

    function createDecoratorForService($delegate) {

        decorate($delegate, $delegate);

        return $delegate;
    }

    function createDecorator($delegate) {
        if (angular.isFunction($delegate)) {
            return createDecoratorForFunctionLikeService($delegate);
        }
        return createDecoratorForService($delegate);
    }

Although this approach has proven to be workable, as time has shown, this approach has its own disadvantages. The main one is that it is impossible to determine which zone will be (root or angular) for a specific part of the hybrid app. For normal operation Angular2+ requires that it is always in the Angular zone, and decorators can cause the Angular2+ components to be in the root zone. ngUpgrade implies that the Angular zone covers most of the application.

To avoid this, there was a 2nd fix attempt with a deeper analysis of the reasons, abandoning decorators and returning to the original issue.

Now we need to understand what code makes the zone unstable. For this, zone.js has special utilities, such as _TaskTrackingZoneSpec_ (_\node_modules\zone.js\dist\task-tracking.js_), which allows to track which tasks are in the zone and where they were created (creationLocation). Also, at the start of the Angular2+ part of the app, save the Angular zone (window as any).a9Zone = (upgradeModule as any).ngZone._inner; and add this code somewhere

$rootScope.$watch(function () {
    if (window.a9Zone) {
        console.log("Angular2+ task count:", window.a9Zone._zoneDelegate._taskCounts);

        // Optional.
        window.getAllAngularTestabilities().forEach(_ => {
            _.taskTrackingZone.macroTasks.forEach(t => {
                console.log(t, t.creationLocation);
            });
        });
    }
});

Thus, find the places in the application where the microtasks and macrotasks are in the zone for a long time (_microTasks.lenght! == 0 && macroTasks.lenght! == 0_).

Then you need to get this code out of the Angular zone. To do this, wrap code in ngZone.runOutsideAngular. However, do it very carefully and make sure that the root zone did not appear where it is not needed.

After the Angular zone becomes stable, the Protractor will no longer fall with timeouts.

Hope this helps.

I came up with a temporary hack to solve this issue by overriding the waitForAngular function with the below logic.

onPrepare: function() {

            var script = "var callback = arguments[arguments.length - 1];" +
                "angular.element(document.querySelector(\"body\")).injector()"+
                ".get(\"$browser\").notifyWhenNoOutstandingRequests(callback)");

            browser.waitForAngular = function() {
                return browser.executeAsyncScript(script)
            };
}
Was this page helpful?
0 / 5 - 0 ratings