diff --git a/lib/angular/wait.js b/lib/angular/wait.js
index 2c6d255..0b6052a 100644
--- a/lib/angular/wait.js
+++ b/lib/angular/wait.js
@@ -1,67 +1,136 @@
 /**
  * Wait until Angular has finished rendering and has
  * no outstanding $http calls before continuing. The specific Angular app
- * is determined by the rootSelector.
+ * is determined by the rootSelector. Copied from Protractor 5.
  *
  * Asynchronous.
  *
  * @param {string} rootSelector The selector housing an ng-app
- * @param {boolean} ng12Hybrid Flag set if app is a hybrid of angular 1 and 2
  * @param {function(string)} callback callback. If a failure occurs, it will
  *     be passed as a parameter.
  */
-exports.NG_WAIT_FN = function(rootSelector, ng12Hybrid, callback) {
-  var el = document.querySelector(rootSelector);
-
+function waitForAngular(rootSelector, callback) {
   try {
-    if (!ng12Hybrid && window.getAngularTestability) {
+    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.');
+      }
+    } else if (rootSelector && window.getAngularTestability) {
+      var el = document.querySelector(rootSelector);
       window.getAngularTestability(el).whenStable(callback);
-      return;
-    }
-    if (!window.angular) {
+    } else if (window.getAllAngularTestabilities) {
+      var testabilities = window.getAllAngularTestabilities();
+      var count = testabilities.length;
+      var decrement = function() {
+        count--;
+        if (count === 0) {
+          callback();
+        }
+      };
+      testabilities.forEach(function(testability) {
+        testability.whenStable(decrement);
+      });
+    } else if (!window.angular) {
       throw new Error('window.angular is 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');
-    }
-    if (angular.getTestability) {
-      angular.getTestability(el).whenStable(callback);
+    } 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 {
-      if (!angular.element(el).injector()) {
-        throw new Error('root element (' + rootSelector + ') has no injector.' +
-            ' this may mean it is not inside ng-app.');
-      }
-      angular.element(el).injector().get('$browser').
-      notifyWhenNoOutstandingRequests(callback);
+      throw new Error('Cannot get testability API for unknown angular ' +
+          'version "' + window.angular.version + '"');
     }
   } catch (err) {
     callback(err.message);
   }
 };
 
-/**
- * Wait until all Angular2 applications on the page have become stable.
+/* Tries to find $$testability and possibly $injector for an ng1 app
  *
- * Asynchronous.
+ * By default, doesn't care about $injector if it finds $$testability.  However,
+ * these priorities can be reversed.
  *
- * @param {function(string)} callback callback. If a failure occurs, it will
- *     be passed as a parameter.
+ * @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
  */
-exports.NG2_WAIT_FN = function(callback) {
-  try {
-    var testabilities = window.getAllAngularTestabilities();
-    var count = testabilities.length;
-    var decrement = function() {
-      count--;
-      if (count === 0) {
-        callback();
+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};
+        }
       }
-    };
-    testabilities.forEach(function(testability) {
-      testability.whenStable(decrement);
-    });
-  } catch (err) {
-    callback(err.message);
+    } 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);
diff --git a/lib/bin.ts b/lib/bin.ts
index e70edb3..7505d00 100644
--- a/lib/bin.ts
+++ b/lib/bin.ts
@@ -15,7 +15,7 @@ if (argv.help) {
   process.exit(0);
 }
 
-const proxy = new BlockingProxy(argv.seleniumAddress, argv.rootElement);
+const proxy = new BlockingProxy(argv.seleniumAddress);
 if (argv.logDir) {
   proxy.enableLogging(argv.logDir);
 }
diff --git a/lib/blockingproxy.ts b/lib/blockingproxy.ts
index 290d1d7..7060467 100644
--- a/lib/blockingproxy.ts
+++ b/lib/blockingproxy.ts
@@ -5,6 +5,7 @@ import {parseWebDriverCommand} from './webdriverCommands';
 import {WebDriverLogger} from './webdriverLogger';
 
 let angularWaits = require('./angular/wait.js');
+export const BP_PREFIX = 'bpproxy';
 
 /**
  * The stability proxy is an http server responsible for intercepting
@@ -15,23 +16,22 @@ export class BlockingProxy {
   seleniumAddress: string;
 
   // The ng-app root to use when waiting on the client.
-  rootElement = '';
-  ng12hybrid = false;
-  stabilityEnabled: boolean;
+  rootSelector = '';
+  waitEnabled: boolean;
   server: http.Server;
   logger: WebDriverLogger;
 
-  constructor(seleniumAddress, rootElement?) {
+  constructor(seleniumAddress) {
     this.seleniumAddress = seleniumAddress;
-    this.rootElement = rootElement || 'body';
-    this.stabilityEnabled = true;
+    this.rootSelector = '';
+    this.waitEnabled = true;
     this.server = http.createServer(this.requestListener.bind(this));
   }
 
   waitForAngularData() {
     return JSON.stringify({
       script: 'return (' + angularWaits.NG_WAIT_FN + ').apply(null, arguments);',
-      args: [this.rootElement, this.ng12hybrid]
+      args: [this.rootSelector]
     });
   }
 
@@ -39,7 +39,7 @@ export class BlockingProxy {
    * This command is for the proxy server, not to be forwarded to Selenium.
    */
   static isProxyCommand(commandPath: string) {
-    return (commandPath.split('/')[1] === 'stabilize_proxy');
+    return (commandPath.split('/')[1] === BP_PREFIX);
   }
 
   /**
@@ -71,13 +71,20 @@ export class BlockingProxy {
     this.logger = logger;
   }
 
+  /**
+   * Change the parameters used by the wait function.
+   */
+  setWaitParams(rootEl) {
+    this.rootSelector = rootEl;
+  }
+
   /**
    * Return true if the requested method should trigger a stabilize first.
    *
    * @param {string} commandPath Original request url.
    */
   shouldStabilize(commandPath) {
-    if (!this.stabilityEnabled) {
+    if (!this.waitEnabled) {
       return false;
     }
 
@@ -135,14 +142,29 @@ export class BlockingProxy {
   handleProxyCommand(message, data, response) {
     let command = message.url.split('/')[2];
     switch (command) {
-      case 'enabled':
+      case 'waitEnabled':
+        if (message.method === 'GET') {
+          response.writeHead(200);
+          response.write(JSON.stringify({value: this.waitEnabled}));
+          response.end();
+        } else if (message.method === 'POST') {
+          response.writeHead(200);
+          this.waitEnabled = JSON.parse(data).value;
+          response.end();
+        } else {
+          response.writeHead(405);
+          response.write('Invalid method');
+          response.end();
+        }
+        break;
+      case 'waitParams':
         if (message.method === 'GET') {
           response.writeHead(200);
-          response.write(JSON.stringify({value: this.stabilityEnabled}));
+          response.write(JSON.stringify({rootSelector: this.rootSelector}));
           response.end();
         } else if (message.method === 'POST') {
           response.writeHead(200);
-          this.stabilityEnabled = JSON.parse(data).value;
+          this.rootSelector = JSON.parse(data).rootSelector;
           response.end();
         } else {
           response.writeHead(405);
diff --git a/lib/client.ts b/lib/client.ts
index 681a483..71a6284 100644
--- a/lib/client.ts
+++ b/lib/client.ts
@@ -1,5 +1,6 @@
 import * as http from 'http';
 import * as url from 'url';
+import {BP_PREFIX} from './blockingproxy';
 
 export class BPClient {
   hostname: string;
@@ -11,10 +12,16 @@ export class BPClient {
     this.port = parseInt(bpUrl.port);
   }
 
-  setSynchronization(enabled: boolean) {
+  /**
+   * Toggle whether waiting for Angular is enabled.
+   *
+   * @param enabled Whether or not to enable waiting for angular.
+   * @returns {Promise<T>}
+   */
+  setWaitEnabled(enabled: boolean): Promise<any> {
     return new Promise((resolve, reject) => {
       let options =
-          {host: this.hostname, port: this.port, method: 'POST', path: '/stabilize_proxy/enabled'};
+          {host: this.hostname, port: this.port, method: 'POST', path: `/${BP_PREFIX}/waitEnabled`};
 
       let request = http.request(options, (response) => {
         response.on('data', () => {});
@@ -28,9 +35,41 @@ export class BPClient {
     });
   }
 
-  isSyncEnabled() {
+  /**
+   * A CSS Selector for a DOM element within your Angular application.
+   * BlockingProxy will attempt to automatically find your application, but it is
+   * necessary to set rootElement in certain cases.
+   *
+   * In Angular 1, BlockingProxy will use the element your app bootstrapped to by
+   * default.  If that doesn't work, it will then search for hooks in `body` or
+   * `ng-app` elements (details here: https://git.io/v1b2r).
+   *
+   * In later versions of Angular, BlockingProxy will try to hook into all angular
+   * apps on the page. Use rootElement to limit the scope of which apps
+   * BlockingProxy waits for and searches within.
+   *
+   * @param rootSelector A selector for the root element of the Angular app.
+   */
+  setWaitParams(rootSelector: string): Promise<any> {
+    return new Promise((resolve, reject) => {
+      let options =
+          {host: this.hostname, port: this.port, method: 'POST', path: `/${BP_PREFIX}/waitParams`};
+
+      let request = http.request(options, (response) => {
+        response.on('data', () => {});
+        response.on('error', (err) => reject(err));
+        response.on('end', () => {
+          resolve();
+        });
+      });
+      request.write(JSON.stringify({rootSelector: rootSelector}));
+      request.end();
+    });
+  }
+
+  isWaitEnabled() {
     return new Promise((res) => {
-      let options = {host: this.hostname, port: this.port, path: '/stabilize_proxy/enabled'};
+      let options = {host: this.hostname, port: this.port, path: `/${BP_PREFIX}/waitEnabled`};
 
       http.get(options, (response) => {
         let body = '';
diff --git a/lib/config.ts b/lib/config.ts
index 41402a9..1499e0a 100644
--- a/lib/config.ts
+++ b/lib/config.ts
@@ -8,7 +8,6 @@ export interface Config {
   seleniumAddress?: string;
   logDir?: string;
   port?: number;
-  rootElement?: string;
 }
 
 const opts: minimist.Opts = {
@@ -22,7 +21,6 @@ const opts: minimist.Opts = {
   default: {
     port: process.env.BP_PORT || 0,
     seleniumAddress: process.env.BP_SELENIUM_ADDRESS || 'http://localhost:4444/wd/hub',
-    rootElement: 'body'
   }
 };
 
diff --git a/spec/e2e/ng1_polling_spec.ts b/spec/e2e/ng1_polling_spec.ts
new file mode 100644
index 0000000..b3cae6a
--- /dev/null
+++ b/spec/e2e/ng1_polling_spec.ts
@@ -0,0 +1,39 @@
+import * as webdriver from 'selenium-webdriver';
+
+import {BlockingProxy} from '../../lib/blockingproxy';
+
+import {getTestEnv} from './environment';
+
+const By = webdriver.By;
+
+describe('disabling waiting as needed', function() {
+  let driver: webdriver.WebDriver;
+  let bp: BlockingProxy;
+
+  beforeAll(() => {
+    ({driver, bp} = getTestEnv());
+  });
+
+  beforeEach(async() => {
+    await driver.get('http://localhost:8081/ng1/#/polling');
+  });
+
+  it('avoids timeouts', async() => {
+    bp.waitEnabled = true;
+
+    let startButton = await driver.findElement(By.id('pollstarter'));
+
+    let count = await driver.findElement(By.id('count'));
+    expect(await count.getText()).toEqual('0');
+
+    await startButton.click();
+
+    bp.waitEnabled = false;
+
+    expect(await count.getText()).toBeGreaterThan(-1);
+
+    await driver.sleep(2000);
+
+    expect(await count.getText()).toBeGreaterThan(1);
+  });
+});
diff --git a/spec/unit/client_spec.ts b/spec/unit/client_spec.ts
index 58e4887..4db8384 100644
--- a/spec/unit/client_spec.ts
+++ b/spec/unit/client_spec.ts
@@ -4,21 +4,31 @@ describe('BlockingProxy Client', () => {
   let bp: BlockingProxy;
   let client: BPClient;
 
-  // TODO dynamically find an open port
-  const BP_PORT = 4111;
-
-  beforeEach(() => {
+  beforeAll(() => {
     bp = new BlockingProxy('http://localhost:3111');
-    bp.listen(BP_PORT);
-    client = new BPClient(`http://localhost:${BP_PORT}`);
+    let bpPort = bp.listen(0);
+    client = new BPClient(`http://localhost:${bpPort}`);
+  });
+
+  it('should toggle waiting', async() => {
+    expect(bp.waitEnabled).toBe(true);
+
+    await client.setWaitEnabled(false);
+    expect(bp.waitEnabled).toBe(false);
+  });
+
+  it('can get whether wait is enabled', async() => {
+    bp.waitEnabled = true;
+    expect(await client.isWaitEnabled()).toBeTruthy();
+    bp.waitEnabled = false;
+    expect(await client.isWaitEnabled()).toBeFalsy();
   });
 
-  it('should set synchronization', (done) => {
-    expect(bp.stabilityEnabled).toBe(true);
+  it('allows changing the root selector', async() => {
+    bp.rootSelector = '';
+    const newRoot = 'div#app';
 
-    client.setSynchronization(false).then(() => {
-      expect(bp.stabilityEnabled).toBe(false);
-      done();
-    });
+    await client.setWaitParams(newRoot);
+    expect(bp.rootSelector).toBe(newRoot);
   });
-});
\ No newline at end of file
+});
diff --git a/spec/unit/proxy_spec.ts b/spec/unit/proxy_spec.ts
index 0e26cbe..e8ac5c5 100644
--- a/spec/unit/proxy_spec.ts
+++ b/spec/unit/proxy_spec.ts
@@ -3,6 +3,6 @@ import {BlockingProxy} from '../../lib/blockingproxy';
 describe('BlockingProxy', () => {
   it('should be able to be created', () => {
     let proxy = new BlockingProxy(8111);
-    expect(proxy.stabilityEnabled).toBe(true);
+    expect(proxy.waitEnabled).toBe(true);
   });
 });
diff --git a/testapp/ng1/app.js b/testapp/ng1/app.js
index d462e4c..53d89a7 100644
--- a/testapp/ng1/app.js
+++ b/testapp/ng1/app.js
@@ -5,6 +5,7 @@
 angular.module('myApp', ['ngRoute', 'myApp.appVersion']).
   config(['$routeProvider', function($routeProvider) {
     $routeProvider.when('/async', {templateUrl: 'async/async.html', controller: AsyncCtrl});
+    $routeProvider.when('/polling', {templateUrl: 'polling/polling.html', controller: PollingCtrl});
     $routeProvider.when('/interaction',
         {templateUrl: 'interaction/interaction.html', controller: InteractionCtrl});
     $routeProvider.when('/slowloader', {
diff --git a/testapp/ng1/polling/polling.html b/testapp/ng1/polling/polling.html
index 789da57..0d50de6 100644
--- a/testapp/ng1/polling/polling.html
+++ b/testapp/ng1/polling/polling.html
@@ -1,4 +1,4 @@
 <div>This view shows a controller which uses a polling mechanism to
   contact the server. It is constantly using angular's $timeout.</div>
 <button id="pollstarter" ng-click="startPolling()">Start Polling</button>
-<div>{{count}}</div>
+<div id="count">{{count}}</div>