-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Idea: Detect angular pages automatically #3859
Description
Preface: This idea got uglier while researching the implementation details. I think #3858 is probably a better idea, but I'm making this issue anyway just to write everything down
We could replace browser.ignoreSynchronization with browser.synchronizationMode which would be either 'ALWAYS', 'NEVER', or 'DETECT' (I'm open to suggestions for different names). ALWAYS would be equivalent to browser.ignoreSynchronization = true, NEVER would be equivalent to browser.ignoreSynchronization = false, and DETECT would work as follows:
First, we replace testForAngular with the following logic:
function testForAngular(callback: (data: {ver?: number}) => void) {
// Pretend we add `undefined` checks throughout this function
function testForAngular_inner(callback: (data: {ver?: number}) => void) {
if (looksLikeNg1Obj(window.angular) || systemJsHas('angular')) {
callback({ver: 1});
} else if (looksLikeModernNgObj(window.ng || window.angular) || hasTestabilityFunctions(window) || systemJsHas('@angular')) {
callback({ver: 2});
} else {
waitForSystemJS(callback);
}
}
function systemJsHas(packageName: string): boolean {
let sysJs = window.SystemJS || window.System;
// Pretend this for loop syntax works
for (let name in (sysJs.map or _.invert(sysJs.map) or sysJs.defined or sysJs._loader.modules or sysJs._loader.importPromises)) {
if ((new RegExp('(\/|^)' + packageName + '(\/|$)').test(packageName)) {
return true;
}
}
return false;
}
var importDone: {[url: string]: boolean} = {};
function waitForSystemJS(callback: (data: {ver?: number}) => void) {
let sysJs = window.SystemJS || window.System;
let waitForImport = (promise: Promise<any>, url: string) => {
importDone[url] = false;
let onComplete = () => {
importDone[url] = true;
// If all promises are resolved, call testForAngular_inner() again
for (let resolved of importResolved) {
if (!resolved) {
return;
}
}
testForAngular_inner(callback);
};
promise.then(onComplete, onComplete);
}
let newPromises = false;
for (let url in sysJs._loader.importPromises) {
if (sysJs._loader.importPromises[url] && (importDone[url] === undefined)) {
importResolved[url] = false;
waitForImport(sysJs._loader.importPromises[url], url);
}
}
if (!newPromises) {
callback({}); // Give up, no angular
}
}
testForAngular_inner(callback);
}(back when I came up with this idea, I didn't realize what an issue SystemJS was going to be)
Now, in browser.get, we use testForAngular, and based on the result of that we decide if we want to do synchronization. Some pitfalls:
- If navigation happened via something other than
browser.get(e.g. someone clicks on a link), we wouldn't bootstrap and end up using an outdated synchronization setting from a previous page. However, Bootstrapping should (mostly) work with Browser-initiated navigation (e.g. clicking on links) #3857 should address that. SystemJS.definedandSystemJS._loaderare not public APIs- No support for other package managers
- Eventually there will be native package managers, but maybe they'll block js execution like
<script>tags do, or maybe there will be something analogous toDOMContentLoadedfor them. - This could still fail if someone is accessing angular indirectly through another package which has angular hard-coded
Alternative ideas
- Feature request: Allow users to specify which URLs to sync against #3858
- In the examples I've seen, zone.js always seems to be loaded directly via a
<script>tag. If we seewindow.Zone, we could wait a few seconds for angular to show up. - We could make a package like
@protractor/testability-flagand ask users to include it client side in a<script>tag.