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

Commit 8aa7817

Browse files
committed
fix(clientSideScripts): change protractor to support waiting for hybrid app
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
1 parent 15776b8 commit 8aa7817

19 files changed

+469
-58
lines changed

lib/clientsidescripts.js

Lines changed: 83 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function getNg1Hooks(selector, injectorPlease) {
8686
return {$injector: $injector};
8787
}
8888
}
89-
} catch(err) {}
89+
} catch(err) {}
9090
}
9191
function trySelector(selector) {
9292
var els = document.querySelectorAll(selector);
@@ -133,54 +133,96 @@ function getNg1Hooks(selector, injectorPlease) {
133133
* be passed as a parameter.
134134
*/
135135
functions.waitForAngular = function(rootSelector, callback) {
136+
136137
try {
137-
if (window.angular && !(window.angular.version &&
138-
window.angular.version.major > 1)) {
139-
/* ng1 */
140-
var hooks = getNg1Hooks(rootSelector);
141-
if (hooks.$$testability) {
142-
hooks.$$testability.whenStable(callback);
143-
} else if (hooks.$injector) {
144-
hooks.$injector.get('$browser').
145-
notifyWhenNoOutstandingRequests(callback);
146-
} else if (!!rootSelector) {
147-
throw new Error('Could not automatically find injector on page: "' +
148-
window.location.toString() + '". Consider using config.rootEl');
149-
} else {
150-
throw new Error('root element (' + rootSelector + ') has no injector.' +
151-
' this may mean it is not inside ng-app.');
138+
// Wait for both angular1 testability and angular2 testability.
139+
140+
var testCallback = callback;
141+
142+
// Wait for angular1 testability first and run waitForAngular2 as a callback
143+
var waitForAngular1 = function(callback) {
144+
145+
if (window.angular) {
146+
var hooks = getNg1Hooks(rootSelector);
147+
if (!hooks){
148+
callback(); // not an angular1 app
149+
}
150+
else{
151+
if (hooks.$$testability) {
152+
hooks.$$testability.whenStable(callback);
153+
} else if (hooks.$injector) {
154+
hooks.$injector.get('$browser')
155+
.notifyWhenNoOutstandingRequests(callback);
156+
} else if (!!rootSelector) {
157+
throw new Error(
158+
'Could not automatically find injector on page: "' +
159+
window.location.toString() + '". Consider using config.rootEl');
160+
} else {
161+
throw new Error(
162+
'root element (' + rootSelector + ') has no injector.' +
163+
' this may mean it is not inside ng-app.');
164+
}
165+
}
152166
}
153-
} else if (rootSelector && window.getAngularTestability) {
154-
var el = document.querySelector(rootSelector);
155-
window.getAngularTestability(el).whenStable(callback);
156-
} else if (window.getAllAngularTestabilities) {
157-
var testabilities = window.getAllAngularTestabilities();
158-
var count = testabilities.length;
159-
var decrement = function() {
160-
count--;
167+
else {callback();} // not an angular1 app
168+
};
169+
170+
// Wait for Angular2 testability and then run test callback
171+
var waitForAngular2 = function() {
172+
if (window.getAngularTestability) {
173+
if (rootSelector) {
174+
var testability = null;
175+
var el = document.querySelector(rootSelector);
176+
try{
177+
testability = window.getAngularTestability(el);
178+
}
179+
catch(e){}
180+
if (testability) {
181+
return testability.whenStable(testCallback);
182+
}
183+
}
184+
185+
// Didn't specify root element or testability could not be found
186+
// by rootSelector. This may happen in a hybrid app, which could have
187+
// more than one root.
188+
var testabilities = window.getAllAngularTestabilities();
189+
var count = testabilities.length;
190+
191+
// No angular2 testability, this happens when
192+
// going to a hybrid page and going back to a pure angular1 page
161193
if (count === 0) {
162-
callback();
194+
return testCallback();
163195
}
164-
};
165-
testabilities.forEach(function(testability) {
166-
testability.whenStable(decrement);
167-
});
168-
} else if (!window.angular) {
169-
throw new Error('window.angular is undefined. This could be either ' +
196+
197+
var decrement = function() {
198+
count--;
199+
if (count === 0) {
200+
testCallback();
201+
}
202+
};
203+
testabilities.forEach(function(testability) {
204+
testability.whenStable(decrement);
205+
});
206+
207+
}
208+
else {testCallback();} // not an angular2 app
209+
};
210+
211+
if (!(window.angular) && !(window.getAngularTestability)) {
212+
// no testability hook
213+
throw new Error(
214+
'both angularJS testability and angular testability are undefined.' +
215+
' This could be either ' +
170216
'because this is a non-angular page or because your test involves ' +
171217
'client-side navigation, which can interfere with Protractor\'s ' +
172218
'bootstrapping. See http://git.io/v4gXM for details');
173-
} else if (window.angular.version >= 2) {
174-
throw new Error('You appear to be using angular, but window.' +
175-
'getAngularTestability was never set. This may be due to bad ' +
176-
'obfuscation.');
177-
} else {
178-
throw new Error('Cannot get testability API for unknown angular ' +
179-
'version "' + window.angular.version + '"');
180-
}
219+
} else {waitForAngular1(waitForAngular2);} // Wait for angular1 and angular2
220+
// Testability hooks sequentially
221+
181222
} catch (err) {
182223
callback(err.message);
183224
}
225+
184226
};
185227

186228
/**
@@ -277,7 +319,7 @@ function findRepeaterRows(repeater, exact, index, using) {
277319
var row = rows[index] || [], multiRow = multiRows[index] || [];
278320
return [].concat(row, multiRow);
279321
}
280-
functions.findRepeaterRows = wrapWithHelpers(findRepeaterRows, repeaterMatch);
322+
functions.findRepeaterRows = wrapWithHelpers(findRepeaterRows, repeaterMatch);
281323

282324
/**
283325
* Find all rows of an ng-repeat.
@@ -697,7 +739,7 @@ functions.testForAngular = function(attempts, ng12Hybrid, asyncCallback) {
697739
if (n < 1) {
698740
if (definitelyNg1 && window.angular) {
699741
callback({message: 'angular never provided resumeBootstrap'});
700-
} else if (ng12Hybrid && !window.angular) {
742+
} else if (ng12Hybrid && !window.angular) {
701743
callback({message: 'angular 1 never loaded' +
702744
window.getAllAngularTestabilities ? ' (are you sure this app ' +
703745
'uses ngUpgrade? Try un-setting ng12Hybrid)' : ''});

spec/hybrid/async_spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,18 @@ describe('async angular1/2 hybrid using ngUpgrade application', function() {
5454
});
5555
});
5656
});
57+
describe('async angular1/2 hybrid using downgrade application', function() {
58+
it('should be able to click buttons and wait for $timeout', function() {
59+
browser.get('/upgrade?downgrade');
60+
61+
var rootBtn = $$('my-app button').first();
62+
expect(rootBtn.getText()).toEqual('Click Count: 0');
63+
rootBtn.click();
64+
expect(rootBtn.getText()).toEqual('Click Count: 1');
65+
66+
var ng2Btn = $$('ng2 button').first();
67+
expect(ng2Btn.getText()).toEqual('Click Count: 0');
68+
ng2Btn.click();
69+
expect(ng2Btn.getText()).toEqual('Click Count: 1');
70+
});
71+
});

testapp/app.css

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,21 @@ li {
2424

2525
li.left {
2626
left: 0;
27-
}
27+
}
2828

2929
li.left.mid {
30-
left: 25%;
30+
left: 20%;
3131
}
3232

33-
li.right {
34-
right: 0;
33+
li.mid{
34+
left : 40%;
3535
}
3636

3737
li.right.mid {
38-
right: 25%;
38+
left: 60%;
39+
}
40+
41+
li.right {
42+
left: 80%;
3943
}
44+

testapp/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ <h1>Choose Version</h1>
1414
<li class='mid left'>
1515
<a href='upgrade?no_static'>Hybrid (JIT)</a>
1616
</li>
17-
<li class='mid right'>
17+
<li class='mid'>
1818
<a href='upgrade'>Hybrid (AOT)</a>
1919
</li>
20+
<li class='mid right'>
21+
<a href='upgrade?downgrade'>Hybrid (Downgrade)</a>
22+
</li>
2023
<li class="right">
2124
<a href="ng2">Angular 2</a>
2225
</li>

testapp/package.json

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,23 @@
66
"tsc": "tsc"
77
},
88
"dependencies": {
9-
"@angular/common": "2.2.1",
10-
"@angular/compiler": "2.2.1",
11-
"@angular/core": "2.2.1",
12-
"@angular/http": "2.2.1",
13-
"@angular/platform-browser": "2.2.1",
14-
"@angular/platform-browser-dynamic": "2.2.1",
15-
"@angular/router": "3.0.0",
16-
"@angular/upgrade": "2.2.1",
9+
"@angular/common": "5.0.0-beta.7",
10+
"@angular/compiler": "5.0.0-beta.7",
11+
"@angular/core": "5.0.0-beta.7",
12+
"@angular/http": "5.0.0-beta.7",
13+
"@angular/platform-browser": "5.0.0-beta.7",
14+
"@angular/animations": "5.0.0-beta.7",
15+
"@angular/platform-browser-dynamic": "5.0.0-beta.7",
16+
"@angular/router": "5.0.0-beta.7",
17+
"@angular/upgrade": "5.0.0-beta.7",
1718
"@types/angular": "^1.5.20",
1819
"@types/core-js": "^0.9.34",
1920
"@types/node": "^6.0.48",
2021
"core-js": "2.4.1",
2122
"reflect-metadata": "0.1.3",
22-
"rxjs": "5.0.0-beta.12",
23+
"rxjs": "5.4.3",
2324
"systemjs": "0.19.27",
24-
"zone.js": "0.6.25"
25+
"zone.js": "0.8.18"
2526
},
2627
"devDependencies": {
2728
"concurrently": "2.2.0",

testapp/tsconfig-aot.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"module": "commonjs",
5+
"moduleResolution": "node",
6+
"sourceMap": true,
7+
"emitDecoratorMetadata": true,
8+
"experimentalDecorators": true,
9+
"lib": ["es2015", "dom"],
10+
"noImplicitAny": true,
11+
"suppressImplicitAnyIndexErrors": true,
12+
"typeRoots": [
13+
"./node_modules/@types/"
14+
]
15+
},
16+
17+
"files": [
18+
"upgrade/app/downgrade/main.ts",
19+
"upgrade/app/downgrade/ng1.ts",
20+
"upgrade/app/downgrade/ng2.ts"
21+
]
22+
23+
}

testapp/upgrade/app/downgrade/main.js

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testapp/upgrade/app/downgrade/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {downgradeModule} from '@angular/upgrade/static';
2+
declare var angular: angular.IAngularStatic;
3+
4+
import {ng1module} from './ng1';
5+
import {AppModuleNgFactory} from './ng2.ngfactory';
6+
7+
// Bootstrap Ng1 app as usual, but add a downgradedModule for the Angular (2+)
8+
// part of the application.
9+
angular.bootstrap(
10+
document.body, [ng1module.name, downgradeModule(AppModuleNgFactory)]);

testapp/upgrade/app/downgrade/ng1.js

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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"}}}}]

testapp/upgrade/app/downgrade/ng1.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
declare var angular: angular.IAngularStatic;
2+
3+
function ctrl($scope: any, $timeout: any) {
4+
$scope.callCount = 0;
5+
6+
$scope.clickButton = function() {
7+
$timeout(() => {
8+
$scope.callCount++;
9+
}, 1000);
10+
};
11+
}
12+
ctrl.$inject = ['$scope', '$timeout'];
13+
14+
export const ng1module = angular.module('hybrid', []);
15+
16+
ng1module.component('myApp', {
17+
template: `<h3>ng1</h3><button ng-click="clickButton()">Click Count: {{callCount}}</button>
18+
<ng2></ng2>
19+
`,
20+
controller: ctrl
21+
});

0 commit comments

Comments
 (0)