Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
fix(clientSideScripts): change protractor to support waiting for hybr…
Browse files Browse the repository at this point in the history
…id app (#4512)

Change protractor to wait for both angular1 hook and angular2 hook so
that it can wait for hybrid app correctly.
Add an aot hybrid app and testcase to test new change
  • Loading branch information
qiyigg authored Oct 2, 2017
1 parent 15776b8 commit f7e17f3
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 58 deletions.
124 changes: 83 additions & 41 deletions lib/clientsidescripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function getNg1Hooks(selector, injectorPlease) {
return {$injector: $injector};
}
}
} catch(err) {}
} catch(err) {}
}
function trySelector(selector) {
var els = document.querySelectorAll(selector);
Expand Down Expand Up @@ -133,54 +133,96 @@ function getNg1Hooks(selector, injectorPlease) {
* be passed as a parameter.
*/
functions.waitForAngular = function(rootSelector, callback) {

try {
if (window.angular && !(window.angular.version &&
window.angular.version.major > 1)) {
/* ng1 */
var hooks = getNg1Hooks(rootSelector);
if (hooks.$$testability) {
hooks.$$testability.whenStable(callback);
} else if (hooks.$injector) {
hooks.$injector.get('$browser').
notifyWhenNoOutstandingRequests(callback);
} else if (!!rootSelector) {
throw new Error('Could not automatically find injector on page: "' +
window.location.toString() + '". Consider using config.rootEl');
} else {
throw new Error('root element (' + rootSelector + ') has no injector.' +
' this may mean it is not inside ng-app.');
// Wait for both angular1 testability and angular2 testability.

var testCallback = callback;

// Wait for angular1 testability first and run waitForAngular2 as a callback
var waitForAngular1 = function(callback) {

if (window.angular) {
var hooks = getNg1Hooks(rootSelector);
if (!hooks){
callback(); // not an angular1 app
}
else{
if (hooks.$$testability) {
hooks.$$testability.whenStable(callback);
} else if (hooks.$injector) {
hooks.$injector.get('$browser')
.notifyWhenNoOutstandingRequests(callback);
} else if (!!rootSelector) {

This comment has been minimized.

Copy link
@gkalpak

gkalpak Nov 3, 2017

Member

Shouldn't this be !rootSelector?

This comment has been minimized.

Copy link
@qiyigg

qiyigg Jan 17, 2018

Author Contributor

I think so, too. Previously, I just try not to change the original logic and leave it alone.

This comment has been minimized.

Copy link
@qiyigg

qiyigg Jan 17, 2018

Author Contributor

@gkalpak will change it in a separate PR

throw new Error(
'Could not automatically find injector on page: "' +
window.location.toString() + '". Consider using config.rootEl');
} else {
throw new Error(
'root element (' + rootSelector + ') has no injector.' +
' this may mean it is not inside ng-app.');
}
}
}
} else if (rootSelector && window.getAngularTestability) {
var el = document.querySelector(rootSelector);
window.getAngularTestability(el).whenStable(callback);
} else if (window.getAllAngularTestabilities) {
var testabilities = window.getAllAngularTestabilities();
var count = testabilities.length;
var decrement = function() {
count--;
else {callback();} // not an angular1 app
};

// Wait for Angular2 testability and then run test callback
var waitForAngular2 = function() {
if (window.getAngularTestability) {
if (rootSelector) {
var testability = null;
var el = document.querySelector(rootSelector);
try{
testability = window.getAngularTestability(el);
}
catch(e){}
if (testability) {
return testability.whenStable(testCallback);
}
}

// Didn't specify root element or testability could not be found
// by rootSelector. This may happen in a hybrid app, which could have
// more than one root.
var testabilities = window.getAllAngularTestabilities();
var count = testabilities.length;

// No angular2 testability, this happens when
// going to a hybrid page and going back to a pure angular1 page
if (count === 0) {
callback();
return testCallback();
}
};
testabilities.forEach(function(testability) {
testability.whenStable(decrement);
});
} else if (!window.angular) {
throw new Error('window.angular is undefined. This could be either ' +

var decrement = function() {
count--;
if (count === 0) {
testCallback();
}
};
testabilities.forEach(function(testability) {
testability.whenStable(decrement);
});

}
else {testCallback();} // not an angular2 app
};

if (!(window.angular) && !(window.getAngularTestability)) {
// no testability hook
throw new Error(
'both angularJS testability and angular testability are undefined.' +
' This could be either ' +
'because this is a non-angular page or because your test involves ' +
'client-side navigation, which can interfere with Protractor\'s ' +
'bootstrapping. See http://git.io/v4gXM for details');
} else if (window.angular.version >= 2) {
throw new Error('You appear to be using angular, but window.' +
'getAngularTestability was never set. This may be due to bad ' +
'obfuscation.');
} else {
throw new Error('Cannot get testability API for unknown angular ' +
'version "' + window.angular.version + '"');
}
} else {waitForAngular1(waitForAngular2);} // Wait for angular1 and angular2
// Testability hooks sequentially

} catch (err) {
callback(err.message);
}

};

/**
Expand Down Expand Up @@ -277,7 +319,7 @@ function findRepeaterRows(repeater, exact, index, using) {
var row = rows[index] || [], multiRow = multiRows[index] || [];
return [].concat(row, multiRow);
}
functions.findRepeaterRows = wrapWithHelpers(findRepeaterRows, repeaterMatch);
functions.findRepeaterRows = wrapWithHelpers(findRepeaterRows, repeaterMatch);

/**
* Find all rows of an ng-repeat.
Expand Down Expand Up @@ -697,7 +739,7 @@ functions.testForAngular = function(attempts, ng12Hybrid, asyncCallback) {
if (n < 1) {
if (definitelyNg1 && window.angular) {
callback({message: 'angular never provided resumeBootstrap'});
} else if (ng12Hybrid && !window.angular) {
} else if (ng12Hybrid && !window.angular) {
callback({message: 'angular 1 never loaded' +
window.getAllAngularTestabilities ? ' (are you sure this app ' +
'uses ngUpgrade? Try un-setting ng12Hybrid)' : ''});
Expand Down
15 changes: 15 additions & 0 deletions spec/hybrid/async_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,18 @@ describe('async angular1/2 hybrid using ngUpgrade application', function() {
});
});
});
describe('async angular1/2 hybrid using downgrade application', function() {
it('should be able to click buttons and wait for $timeout', function() {
browser.get('/upgrade?downgrade');

var rootBtn = $$('my-app button').first();
expect(rootBtn.getText()).toEqual('Click Count: 0');
rootBtn.click();
expect(rootBtn.getText()).toEqual('Click Count: 1');

var ng2Btn = $$('ng2 button').first();
expect(ng2Btn.getText()).toEqual('Click Count: 0');
ng2Btn.click();
expect(ng2Btn.getText()).toEqual('Click Count: 1');
});
});
15 changes: 10 additions & 5 deletions testapp/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,21 @@ li {

li.left {
left: 0;
}
}

li.left.mid {
left: 25%;
left: 20%;
}

li.right {
right: 0;
li.mid{
left : 40%;
}

li.right.mid {
right: 25%;
left: 60%;
}

li.right {
left: 80%;
}

5 changes: 4 additions & 1 deletion testapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ <h1>Choose Version</h1>
<li class='mid left'>
<a href='upgrade?no_static'>Hybrid (JIT)</a>
</li>
<li class='mid right'>
<li class='mid'>
<a href='upgrade'>Hybrid (AOT)</a>
</li>
<li class='mid right'>
<a href='upgrade?downgrade'>Hybrid (Downgrade)</a>
</li>
<li class="right">
<a href="ng2">Angular 2</a>
</li>
Expand Down
21 changes: 11 additions & 10 deletions testapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@
"tsc": "tsc"
},
"dependencies": {
"@angular/common": "2.2.1",
"@angular/compiler": "2.2.1",
"@angular/core": "2.2.1",
"@angular/http": "2.2.1",
"@angular/platform-browser": "2.2.1",
"@angular/platform-browser-dynamic": "2.2.1",
"@angular/router": "3.0.0",
"@angular/upgrade": "2.2.1",
"@angular/common": "5.0.0-beta.7",
"@angular/compiler": "5.0.0-beta.7",
"@angular/core": "5.0.0-beta.7",
"@angular/http": "5.0.0-beta.7",
"@angular/platform-browser": "5.0.0-beta.7",
"@angular/animations": "5.0.0-beta.7",
"@angular/platform-browser-dynamic": "5.0.0-beta.7",
"@angular/router": "5.0.0-beta.7",
"@angular/upgrade": "5.0.0-beta.7",
"@types/angular": "^1.5.20",
"@types/core-js": "^0.9.34",
"@types/node": "^6.0.48",
"core-js": "2.4.1",
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.12",
"rxjs": "5.4.3",
"systemjs": "0.19.27",
"zone.js": "0.6.25"
"zone.js": "0.8.18"
},
"devDependencies": {
"concurrently": "2.2.0",
Expand Down
23 changes: 23 additions & 0 deletions testapp/tsconfig-aot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"./node_modules/@types/"
]
},

"files": [
"upgrade/app/downgrade/main.ts",
"upgrade/app/downgrade/ng1.ts",
"upgrade/app/downgrade/ng2.ts"
]

}
9 changes: 9 additions & 0 deletions testapp/upgrade/app/downgrade/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions testapp/upgrade/app/downgrade/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {downgradeModule} from '@angular/upgrade/static';
declare var angular: angular.IAngularStatic;

import {ng1module} from './ng1';
import {AppModuleNgFactory} from './ng2.ngfactory';

// Bootstrap Ng1 app as usual, but add a downgradedModule for the Angular (2+)
// part of the application.
angular.bootstrap(
document.body, [ng1module.name, downgradeModule(AppModuleNgFactory)]);
17 changes: 17 additions & 0 deletions testapp/upgrade/app/downgrade/ng1.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions testapp/upgrade/app/downgrade/ng1.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"__symbolic":"module","version":3,"metadata":{"ng1module":{"__symbolic":"error","message":"Reference to a local symbol","line":0,"character":12,"context":{"name":"angular"}}}},{"__symbolic":"module","version":1,"metadata":{"ng1module":{"__symbolic":"error","message":"Reference to a local symbol","line":0,"character":12,"context":{"name":"angular"}}}}]
21 changes: 21 additions & 0 deletions testapp/upgrade/app/downgrade/ng1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
declare var angular: angular.IAngularStatic;

function ctrl($scope: any, $timeout: any) {
$scope.callCount = 0;

$scope.clickButton = function() {
$timeout(() => {
$scope.callCount++;
}, 1000);
};
}
ctrl.$inject = ['$scope', '$timeout'];

export const ng1module = angular.module('hybrid', []);

ng1module.component('myApp', {
template: `<h3>ng1</h3><button ng-click="clickButton()">Click Count: {{callCount}}</button>
<ng2></ng2>
`,
controller: ctrl
});
Loading

0 comments on commit f7e17f3

Please sign in to comment.