protractor: Protractor fails when app is bootstrapped with ngUpgrade

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.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 13
  • Comments: 51 (10 by maintainers)

Most upvoted comments

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.

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 😃

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?

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.

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.

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.

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.

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

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)

Same issue here.

@vikerman is there any update or workaround?

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)

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.

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

@vikerman Is there any update on this?

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)
            };
}

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);
    });
  };
})

@maurycyg patchTimer -> patchMethod function should do the trick

@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.