Using protractor with ng-upgrade - angularjs

I am working on migrating my AngularJS (1.6) app to Angular (4) and now have a hybrid application, bootstrapped with NgUpgrade.
However, this seems to have completely broken my Protractor tests.
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 can't understand why this is any different and no amount of changing things around seems to help, so any guidance would be most welcome!
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.

What is funny is I asked the exact same question (+github) 15 months ago about angular 1 to angular 2:
Protractor + Hybrid Angular 1+2 Application = Fail
Without any solution I rolled back to angular 1. Today we decided to try again to move up from angular 1 to 4 like you, and landed on your question because we still have the exact same problem...
Well, not so funny actually... I can't understand why a so important feature is put apart like that. AngularJs was supposed to be actively maintained because of the breaking change to Angular 2+! It's a little disappointing (sorry for those expecting an real answer, I will post if updates)...
Edit: I went to React

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

Related

Angular Ng1/Ng2 hybrid app throwing 'No provider for $scope' exception

I'm trying to build an hybrid Ng1/Ng2 app with NgUpgrade as an intermediate step in a migration towards Angular 2.
The bootstrap seems to work:
platformBrowserDynamic().bootstrapModule(AppModule).then( () => {
adapter.bootstrap(document.body, [Ng1AppModule.name]);
});
But I'm struggling at using a simple Ng1 directive in an Ng2 component, see this very simple plunker: https://plnkr.co/edit/gMSwKKIgEkwcijCCaCMW?p=preview
Even after having upgraded the Ng1 directive and downgraded the Ng2 component, the browser sends a "No provider for $scope!" exception.
What have I missed ?
I know that some people have already reported this exception, but it looks like NgUpgrade has evolved a lot since the beta of Angular 2, and I can't find the appropriate answer for current Angular 2 release.
Found it !
My mistake was to try to bootstrap directly a ng2 module, but it looks like you have to put a ng1 component at top level of the DOM (yes it's written somewhere in the official ngUpgrade guide). Here is the correct updated plunker with a ng1 root component, that calls a ng2 module, that calls another ng1 component.
const myNg1Root = {
template: `
<p>This is the Angular 1 root component</p>
<ng2root></ng2root>
`,
};

How do I rid my protractor tests of this error: Error while waiting for Protractor to sync with the page: {}

I have an Angular application that integrates with AudioEye, Twitter, and some other third party applications. We use lazy loading to load our components so our app has no rootElement app. Our login page is a different Angular application. When I run my test suite I keep seeing this error:
Error while waiting for Protractor to sync with the page: {}
Each time the tests run I get different results due to this error, and I am wondering I can resolve the issue. Any ideas or theories would be greatly appreciated. Thanks in advance.
STACK:
Cucumber 9.5
Protractor: 2.5.1
node: 0.12
I think these cases are still solved by turning the Protractor-to-Angular synchronization off. Put the following into onPrepare() in your config, or into a beforeEach/beforeAll of your test:
browser.ignoreSynchronization = true;
Note that, unfortunately, this would eliminate one of the advantages of using Protractor for testing AngularJS applications. Now, the tests may require additional explicit waits via browser.wait().
Follow-up links:
Using Protractor Test with Bootstrapped AngularJS
Protractor and angular manual bootstrap
In your config file add- rootElement : 'html'. and when you navigate to non-angualar-page use browser.ignoreSynchronization=true and use protractor commands.

Running more than 10 tests on karma using jasmine causes: "ERROR: Some of your tests did a full page reload!"

So, this is my first project where I'm using Karma and Jasmine to unit test my angularJS code. Used Yeoman angular generator for the setup.
As soon as I reached 11 tests, I got an error saying "Some of your tests did a full page reload". I'm not doing any tests that would trigger a reload.
Digging deeper I saw the same issue being referenced on Github.
https://github.com/jasmine/jasmine/issues/366 -- (FuzzySockets comments)
The problem seems to stem from a line of code in jasmine-core
https://github.com/jasmine/jasmine/blob/master/lib/jasmine-core/jasmine.js
To avoid overflow of stack, the maximumSpecCallbackDepth is set to 20. And every time currentSpecCallbackDepth exceeds that, further tests are executed on a new stack by using the setTimout function.
This is the line that seems to cause problems and makes karma throw the error. (I've verified this by invoking the setTimeout method in my own unit test, and it threw the same error).
If change the maximumSpecCallbackDepth to 100, my tests run fine, and no errors are thrown at the end
Has anyone seen this issue and know a fix? I'm using the latest versions of karma(0.13.15) and jasmine(2.4.1).
I haven't really messed around too much with the default grunt or karma config that came with yeoman generated ones, except that I'm using the chrome launcher instead of the default phantomJS, so I don't understand how everyone else is not facing the same issue here.
+1 for this issue. As u said, it caused by maximumSpecCallbackDepth limitation, but I didn't find no fix for this issue so far. You probably could track issue here https://github.com/karma-runner/karma/issues/1101 .
One temporary solution is to reduce nested 'describe' block in your project.
I got a similar issue where the angular injections in the global beforeEach stopped working and all tests failed after the 20 limit of maximumSpecCallbackDepth.
During my investigations, I found out that angular-mock doesn't play well with the setTimeout done in jasmine when that limit is reached.
The following code that is given as example everywhere will create a new injector on each test case:
var yourService;
beforeEach(module('app'));
beforeEach(inject(function(_yourService_) {
yourService = _yourService_;
}));
Instead, you could do the following, which will use a single injector and register your modules only once.
var yourService;
module.sharedInjector();
beforeAll(module('app'));
beforeEach(inject(function(_yourService_) {
yourService = _yourService_;
}));
Hope this might help others as it took me almost a week to find out that this was the root cause of the issue and not Jasmine itself like some people think on github.

Cordova + Angular: How to test?

I've just recently started fooling around with Cordova for a mobile App. For now the code base is quite small. I've also used AngularJS to drive my javascript. Now that I have reached a stable state, I would like to investigate ways to unit test the code I just wrote. Thing is, I'm not finding any useful resource for the pair. Angular suggests either Karma (unit) or Protractor (scenarios), but I'm finding quite hard to bootstrap them both with a Cordova App, since this is not supposed to run inside the browser, but within some kind of container where cordova can be loaded. Are there already some good test-driven approaches in the open source market regarding test driven development of hybrid apps?
I think that correct approach would be to have cordova.mocks.js included in tests that will mock out cordova dependencies. And then unittest as usual.
I think there is no way at the moment to test the parts of cordova that would call functionality from plugins.
But you could use Karma or Protractor as you would in the browser (eventually with some mocks for cordova and cordova plugins), which require some additional if conditionals to run the app without a physical device
Ie if (window.cordova && cordova.plugins.thePluginExample) { /* Code that uses plugins [...] */ }
You can use "phonegap server" even if you're using cordova, also you can run on the device with cordova run <platform> --device.
You can track issues on the CLI output of both methods.
To help others who get here with the same question as I did...
You probably don't need to bootstrap with Cordova. Use mocks as stand-ins.
Since Cordova attaches to window, you can write your app code to inject $window and mock cordova with with standard mocking.
Example with mocha / chai:
/**
* Test case for AngularJS module that does something when platform = 'ios'
*/
describe('platform = "ios"', function() {
var $window;
beforeEach('inject', inject(function(_$window_) {
$window = _$window_;
$window.cordova = {
platformId: 'ios',
}
}));
it('verifies cordova mock platform = "ios"', function() {
expect($window.cordova.platformId).to.equal('ios');
});
it('does something', function() {
// ...
});
});

Multiple versions of AngularJS in one page

What issues might I experience in having two different versions of AngularJS loaded into one page?
Obviously this seems like a stupid thing to do, but my use case is a page that uses AngularJS incorporating a third-party component that drags in its preferred version of AngularJS.
Update:
Found some info:
https://groups.google.com/forum/#!msg/angular/G8xPyD1F8d0/u1QNDNvcwW4J
https://github.com/angular/angular.js/pull/1535
Angular is really not prepared to co-exist with other version. But it's feasible.
First of all load angular library and make sure that before loading window.angular is empty:
<script src="vendor/angular/1.2.0/angular.min.js"></script>
<script src="app/app2.js"></script>
<script>
var angular2 = angular;
window.angular = null; // here we're cleaning angular reference
</script>
<script src="vendor/angular/1.0.5/angular.js"></script>
<script src="app/app1.js"></script>
<script>
var angular1 = angular;
</script>
Note that each application (app1.js, app2.js) for each version of angular should be loaded immediately after loading angular library.
Each JavaScript file of the application shoud be wrapped in self executing function (function(angular) { ... })(angular). Look at the example of app2.js:
(function(angular) {
angular.module('myApp2', []).
controller('App2Ctrl', function($scope) {
$scope.$watchCollection('[a, b, c]', function() {
console.log(angular.version.full);
});
});
})(angular);
Note that here I'm using $watchCollection which is only available for angular 1.2.x. By providing scope of anonymous function for each file you are forcing application to reach angular property instead of window.angular property.
Finally you have to bootstrap the application using manuall method:
<body>
<div id="myApp1" ng-controller="App1Ctrl">
</div>
<div id="myApp2" ng-controller="App2Ctrl">
</div>
<script>
angular1.bootstrap(document.getElementById('myApp1'), ['myApp1']);
angular2.bootstrap(document.getElementById('myApp2'), ['myApp2']);
</script>
</body>
Working plunker here. After running please check console window to see logged versions of angular used.
Great question! Like you, we were unable to uncover much on this topic...we are in the process of deploying a version of Angular with our product which will be embedded within client websites that could also have Angular already loaded.
Here is what we have done:
Modify the Angular source - do not use "window.angular" or "angular" in your implementation, choose some other variable or object property to house it. If you do use "window.angular" and/or "angular", it could break both applications if the versions are different.
Rename all delivered objects (directives, etc); otherwise, the other version of angular could attempt to process your directives.
Rename all CSS classes used by Angular (ng-cloak, etc). This will allow you to style your version of Angular separately from the other version.
Manually bootstrap your application, as described by 'kseb' above.
If you are going to completely name space AngularJS as I have described here, take care to not do a global search and replace because angular does need a "window" reference for registering events, and a few other places.
I just finished doing this at work and I am in the process of testing. I will update this answer with my results.
Matt Burke describes a process to wrap the Angular JS libs in a function to create a closure. Downside is that you cannot load Angular via a public CDN as you have customized the download.
http://www.mattburkedev.com/multiple-angular-versions-on-the-same-page/
Angular 2 will provide a new router with similar features to UI router, but that will also allow to have some routes in Angular 1 and others in Angular 2.
This router is currently being backported to Angular 1, see here a presentation from the developer of the new router explaining how this will work.
The idea behind a common cross-version router with support for both version is to help users upgrade from Angular 1 to Angular 2.

Resources