| /** |
| * Copied from Protractor 5.2.0 |
| * |
| * Wait until Angular has finished rendering and has |
| * no outstanding $http calls before continuing. The specific Angular app |
| * is determined by the rootSelector. |
| * |
| * Asynchronous. |
| * |
| * @param {string} rootSelector The selector housing an ng-app |
| * @param {function(string)} callback callback. If a failure occurs, it will |
| * be passed as a parameter. |
| */ |
| function waitForAngular(rootSelector, callback) { |
| |
| try { |
| // 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) { |
| 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 {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) { |
| return testCallback(); |
| } |
| |
| 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 {waitForAngular1(waitForAngular2);} // Wait for angular1 and angular2 |
| // Testability hooks sequentially |
| |
| } catch (err) { |
| callback(err.message); |
| } |
| |
| }; |
| |
| /* Tries to find $$testability and possibly $injector for an ng1 app |
| * |
| * By default, doesn't care about $injector if it finds $$testability. However, |
| * these priorities can be reversed. |
| * |
| * @param {string=} selector The selector for the element with the injector. If |
| * falsy, tries a variety of methods to find an injector |
| * @param {boolean=} injectorPlease Prioritize finding an injector |
| * @return {$$testability?: Testability, $injector?: Injector} Returns whatever |
| * ng1 app hooks it finds |
| */ |
| function getNg1Hooks(selector, injectorPlease) { |
| function tryEl(el) { |
| try { |
| if (!injectorPlease && angular.getTestability) { |
| var $$testability = angular.getTestability(el); |
| if ($$testability) { |
| return {$$testability: $$testability}; |
| } |
| } else { |
| var $injector = angular.element(el).injector(); |
| if ($injector) { |
| return {$injector: $injector}; |
| } |
| } |
| } catch(err) {} |
| } |
| function trySelector(selector) { |
| var els = document.querySelectorAll(selector); |
| for (var i = 0; i < els.length; i++) { |
| var elHooks = tryEl(els[i]); |
| if (elHooks) { |
| return elHooks; |
| } |
| } |
| } |
| |
| if (selector) { |
| return trySelector(selector); |
| } else if (window.__TESTABILITY__NG1_APP_ROOT_INJECTOR__) { |
| var $injector = window.__TESTABILITY__NG1_APP_ROOT_INJECTOR__; |
| var $$testability = null; |
| try { |
| $$testability = $injector.get('$$testability'); |
| } catch (e) {} |
| return {$injector: $injector, $$testability: $$testability}; |
| } else { |
| return tryEl(document.body) || |
| trySelector('[ng-app]') || trySelector('[ng\\:app]') || |
| trySelector('[ng-controller]') || trySelector('[ng\\:controller]'); |
| } |
| } |
| |
| /* Wraps a function up into a string with its helper functions so that it can |
| * call those helper functions client side |
| * |
| * @param {function} fun The function to wrap up with its helpers |
| * @param {...function} The helper functions. Each function must be named |
| * |
| * @return {string} The string which, when executed, will invoke fun in such a |
| * way that it has access to its helper functions |
| */ |
| function wrapWithHelpers(fun) { |
| var helpers = Array.prototype.slice.call(arguments, 1); |
| if (!helpers.length) { |
| return fun; |
| } |
| var FunClass = Function; // Get the linter to allow this eval |
| return new FunClass( |
| helpers.join(';') + String.fromCharCode(59) + |
| ' return (' + fun.toString() + ').apply(this, arguments);'); |
| } |
| |
| exports.NG_WAIT_FN = wrapWithHelpers(waitForAngular, getNg1Hooks); |