Merge pull request #42 from DrMarcII/master

Clean up to a lot of WebDriver code.
diff --git a/lib/async_helpers.dart b/lib/async_helpers.dart
new file mode 100644
index 0000000..540d2fc
--- /dev/null
+++ b/lib/async_helpers.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library webdriver.async_helpers;
+
+import 'dart:async';
+import 'package:matcher/matcher.dart';
+
+const defaultInterval = const Duration(milliseconds: 500);
+const defaultTimeout = const Duration(seconds: 5);
+
+const clock = const Clock();
+
+Future waitFor(condition(), {matcher: isNotNull,
+    Duration timeout: defaultTimeout,
+    Duration interval: defaultInterval}) => clock.waitFor(condition,
+        matcher: matcher, timeout: timeout, interval: interval);
+
+class Clock {
+  const Clock();
+
+  /// Sleep for the specified time.
+  Future sleep([Duration interval = defaultInterval]) =>
+      new Future.delayed(interval);
+
+  /// The current time.
+  DateTime get now => new DateTime.now();
+
+  /// Waits until [condition] evaluates to a value that matches [matcher] or
+  /// until [timeout] time has passed. If [condition] returns a [Future], then
+  /// uses the value of that [Future] rather than the value of [condition].
+  ///
+  /// If the wait is successful, then the matching return value of [condition]
+  /// is returned. Otherwise, if [condition] throws, then that exception is
+  /// rethrown. If [condition] doesn't throw then an [expect] exception is
+  /// thrown.
+  Future waitFor(condition(), {matcher: isNotNull,
+      Duration timeout: defaultTimeout,
+      Duration interval: defaultInterval}) async {
+    var endTime = now.add(timeout);
+    while (true) {
+      try {
+        var value = await condition();
+        expect(value, matcher);
+        return value;
+      } catch (e) {
+        if (now.isAfter(endTime)) {
+          rethrow;
+        } else {
+          await sleep(interval);
+        }
+      }
+    }
+  }
+}
+
+class Lock {
+  Completer _lock;
+
+  Future acquire() async {
+    while (isHeld) {
+      await _lock.future;
+    }
+    _lock = new Completer();
+    // This return should not be required, but has been added to make analyzer
+    // happy.
+    return null;
+  }
+
+  void release() {
+    if (!isHeld) {
+      throw new StateError('No lock to release');
+    }
+    _lock.complete();
+    _lock = null;
+  }
+
+  bool get isHeld => _lock != null;
+}
diff --git a/lib/src/alert.dart b/lib/src/alert.dart
index 0dfd17f..3a6d8b9 100644
--- a/lib/src/alert.dart
+++ b/lib/src/alert.dart
@@ -1,45 +1,40 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 /// A JavaScript alert(), confirm(), or prompt() dialog
 class Alert extends _WebDriverBase {
-  /**
-   * The text of the JavaScript alert(), confirm(), or
-   * prompt() dialog.
-   */
+  /// The text of the JavaScript alert(), confirm(), or prompt() dialog.
   final String text;
 
   Alert._(this.text, driver) : super(driver, '');
 
-  /**
-   * Accepts the currently displayed alert (may not be the alert for which
-   * this object was created).
-   *
-   * Throws [WebDriverError] no such alert exception if there isn't currently an
-   * alert.
-   */
+  /// Accepts the currently displayed alert (may not be the alert for which this
+  /// object was created).
+  ///
+  ///  Throws [NoSuchAlertException] if there isn't currently an alert.
   Future accept() async {
     await _post('accept_alert');
   }
 
-  /**
-   * Dismisses the currently displayed alert (may not be the alert for which
-   * this object was created).
-   *
-   * Throws [WebDriverError] no such alert exception if there isn't currently an
-   * alert.
-   */
+  /// Dismisses the currently displayed alert (may not be the alert for which
+  /// this object was created).
+  ///
+  ///  Throws [NoSuchAlertException] if there isn't currently an alert.
   Future dismiss() async {
     await _post('dismiss_alert');
   }
 
-  /**
-   * Sends keys to the currently displayed alert (may not be the alert for which
-   * this object was created).
-   *
-   * Throws [WebDriverError] no such alert exception if there isn't currently an
-   * alert.
-   */
+  /// Sends keys to the currently displayed alert (may not be the alert for
+  /// which this object was created).
+  ///
+  /// Throws [NoSuchAlertException] if there isn't currently an alert
   Future sendKeys(String keysToSend) async {
     await _post('alert_text', {'text': keysToSend});
   }
+
+  @override
+  String toString() => '$driver.switchTo.alert[$text]';
 }
diff --git a/lib/src/capabilities.dart b/lib/src/capabilities.dart
index 170993e..8e9a0dc 100644
--- a/lib/src/capabilities.dart
+++ b/lib/src/capabilities.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Capabilities {
diff --git a/lib/src/command_processor.dart b/lib/src/command_processor.dart
index 3f1c3c1..1c40d24 100644
--- a/lib/src/command_processor.dart
+++ b/lib/src/command_processor.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 final ContentType _contentTypeJson =
diff --git a/lib/src/common.dart b/lib/src/common.dart
index 4b5ef82..6f12503 100644
--- a/lib/src/common.dart
+++ b/lib/src/common.dart
@@ -1,11 +1,13 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 const String _ELEMENT = 'ELEMENT';
 
-/**
- * Simple class to provide access to indexed properties such as WebElement
- * attributes or css styles.
- */
+/// Simple class to provide access to indexed properties such as WebElement
+/// attributes or css styles.
 class Attributes extends _WebDriverBase {
   Attributes._(driver, command) : super(driver, command);
 
@@ -28,6 +30,9 @@
   @override
   bool operator ==(other) =>
       other is Size && other.height == this.height && other.width == this.width;
+
+  @override
+  String toString() => 'Size<${height}h X ${width}w>';
 }
 
 class Point {
@@ -46,19 +51,20 @@
   @override
   bool operator ==(other) =>
       other is Point && other.x == this.x && other.x == this.x;
+
+  @override
+  String toString() => 'Point($x, $y)';
 }
 
 abstract class SearchContext {
+  WebDriver get driver;
 
   /// Searches for multiple elements within the context.
   Stream<WebElement> findElements(By by);
 
-  /**
-   * Searchs for an element within the context.
-   *
-   * Throws [WebDriverError] no such element exception if no matching element is
-   * found.
-   */
+  /// Searchs for an element within the context.
+  ///
+  /// Throws [NoSuchElementException] if no matching element is found.
   Future<WebElement> findElement(By by);
 }
 
@@ -101,10 +107,8 @@
   /// Returns an anchor element whose visible text matches the search value.
   const By.linkText(String linkText) : this._('link text', linkText);
 
-  /**
-   * Returns an anchor element whose visible text partially matches the search
-   * value.
-   */
+  /// Returns an anchor element whose visible text partially matches the search
+  /// value.
   const By.partialLinkText(String partialLinkText)
       : this._('partial link text', partialLinkText);
 
@@ -125,112 +129,36 @@
       : this._('css selector', cssSelector);
 
   Map<String, String> toJson() => {'using': _using, 'value': _value};
-}
 
-// TODO(DrMarcII): Create a better WebDriver exception hierarchy.
-class WebDriverError {
-  static const List<String> _errorTypes = const [
-    null,
-    'IndexOutOfBounds',
-    'NoCollection',
-    'NoString',
-    'NoStringLength',
-    'NoStringWrapper',
-    'NoSuchDriver',
-    'NoSuchElement',
-    'NoSuchFrame',
-    'UnknownCommand',
-    'ObsoleteElement',
-    'ElementNotDisplayed',
-    'InvalidElementState',
-    'Unknown',
-    'Expected',
-    'ElementNotSelectable',
-    'NoSuchDocument',
-    'UnexpectedJavascript',
-    'NoScriptResult',
-    'XPathLookup',
-    'NoSuchCollection',
-    'TimeOut',
-    'NullPointer',
-    'NoSuchWindow',
-    'InvalidCookieDomain',
-    'UnableToSetCookie',
-    'UnexpectedAlertOpen',
-    'NoAlertOpen',
-    'ScriptTimeout',
-    'InvalidElementCoordinates',
-    'IMENotAvailable',
-    'IMEEngineActivationFailed',
-    'InvalidSelector',
-    'SessionNotCreatedException',
-    'MoveTargetOutOfBounds'
-  ];
-  static const List<String> _errorDetails = const [
-    null,
-    'IndexOutOfBounds',
-    'NoCollection',
-    'NoString',
-    'NoStringLength',
-    'NoStringWrapper',
-    'NoSuchDriver',
-    'An element could not be located on the page using the given '
-        'search parameters.',
-    'A request to switch to a frame could not be satisfied because the '
-        'frame could not be found.',
-    'The requested resource could not be found, or a request was '
-        'received using an HTTP method that is not supported by the '
-        'mapped resource.',
-    'An element command failed because the referenced element is no '
-        'longer attached to the DOM.',
-    'An element command could not be completed because the element '
-        'is not visible on the page.',
-    'An element command could not be completed because the element is in '
-        'an invalid state (e.g. attempting to click a disabled element).',
-    'An unknown server-side error occurred while processing the command.',
-    'Expected',
-    'An attempt was made to select an element that cannot be selected.',
-    'NoSuchDocument',
-    'An error occurred while executing user supplied JavaScript.',
-    'NoScriptResult',
-    'An error occurred while searching for an element by XPath.',
-    'NoSuchCollection',
-    'An operation did not complete before its timeout expired.',
-    'NullPointer',
-    'A request to switch to a different window could not be satisfied '
-        'because the window could not be found.',
-    'An illegal attempt was made to set a cookie under a different '
-        'domain than the current page.',
-    'A request to set a cookie\'s value could not be satisfied.',
-    'A modal dialog was open, blocking this operation.',
-    'An attempt was made to operate on a modal dialog when one was '
-        'not open.',
-    'A script did not complete before its timeout expired.',
-    'The coordinates provided to an interactions operation are invalid.',
-    'IME was not available.',
-    'An IME engine could not be started.',
-    'Argument was an invalid selector (e.g. XPath/CSS).',
-    'A new session could not be created.',
-    'Target provided for a move action is out of bounds.'
-  ];
-
-  final int statusCode;
-  String type;
-  final String message;
-  String details;
-  final String results;
-
-  WebDriverError(this.statusCode, this.message, [this.results = '']) {
-    if (statusCode < 0 || statusCode > 32) {
-      type = 'External';
-      details = '';
-    } else {
-      type = _errorTypes[statusCode];
-      details = _errorDetails[statusCode];
-    }
-  }
-
+  @override
   String toString() {
-    return '$statusCode $type: $message $results\n$details';
+    var constructor;
+    switch (_using) {
+      case 'link text':
+        constructor = 'linkText';
+        break;
+      case 'partial link text':
+        constructor = 'partialLinkText';
+        break;
+      case 'tag name':
+        constructor = 'tagName';
+        break;
+      case 'class name':
+        constructor = 'className';
+        break;
+      case 'css selector':
+        constructor = 'cssSelector';
+        break;
+      default:
+        constructor = _using;
+    }
+    return 'By.$constructor($_value)';
   }
+
+  @override
+  int get hashCode => _using.hashCode * 3 + _value.hashCode;
+
+  @override
+  bool operator ==(other) =>
+      other is By && other._using == this._using && other._value == this._value;
 }
diff --git a/lib/src/exception.dart b/lib/src/exception.dart
index 675f890..53d5db9 100644
--- a/lib/src/exception.dart
+++ b/lib/src/exception.dart
@@ -1,31 +1,15 @@
-/*
-Copyright 2015 Google Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
 
 part of webdriver;
 
 abstract class WebDriverException {
-  /**
-   * Either the status value returned in the JSON response (preferred) or the
-   * HTTP status code.
-   */
+  /// Either the status value returned in the JSON response (preferred) or the
+  /// HTTP status code.
   final int statusCode;
 
-  /**
-   * A message describing the error.
-   */
+  /// A message describing the error.
   final String message;
 
   factory WebDriverException(
diff --git a/lib/src/keyboard.dart b/lib/src/keyboard.dart
index 3391a56..87e441b 100644
--- a/lib/src/keyboard.dart
+++ b/lib/src/keyboard.dart
@@ -1,12 +1,23 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Keyboard extends _WebDriverBase {
   Keyboard._(driver) : super(driver, '');
 
-  /**
-   * Send [keysToSend] to the active element.
-   */
+  /// Send [keysToSend] to the active element.
   Future sendKeys(String keysToSend) async {
     await _post('keys', {'value': [keysToSend]});
   }
+
+  @override
+  String toString() => '$driver.keyboard';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is Keyboard && other.driver == driver;
 }
diff --git a/lib/src/keys.dart b/lib/src/keys.dart
index 37d669e..a22a4b7 100644
--- a/lib/src/keys.dart
+++ b/lib/src/keys.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Keys {
diff --git a/lib/src/lock.dart b/lib/src/lock.dart
deleted file mode 100644
index 7a4b09d..0000000
--- a/lib/src/lock.dart
+++ /dev/null
@@ -1,24 +0,0 @@
-library webdriver.lock;
-
-import 'dart:async';
-
-class Lock {
-  Completer _lock;
-
-  Future acquire() async {
-    while (isAcquired) {
-      await _lock.future;
-    }
-    _lock = new Completer();
-  }
-
-  void release() {
-    if (!isAcquired) {
-      throw new StateError('No lock to release');
-    }
-    _lock.complete();
-    _lock = null;
-  }
-
-  bool get isAcquired => _lock != null;
-}
diff --git a/lib/src/logs.dart b/lib/src/logs.dart
index d6fd7bd..eaa2ce5 100644
--- a/lib/src/logs.dart
+++ b/lib/src/logs.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Logs extends _WebDriverBase {
@@ -16,16 +20,30 @@
 
     return controller.stream;
   }
+
+  @override
+  String toString() => '$driver.logs';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is Logs && other.driver == driver;
 }
+
 class LogEntry {
   final String message;
-  final int timestamp;
+  final DateTime timestamp;
   final String level;
 
   const LogEntry(this.message, this.timestamp, this.level);
 
-  LogEntry.fromMap(Map map)
-      : this(map['message'], map['timestamp'], map['level']);
+  LogEntry.fromMap(Map map) : this(map['message'],
+          new DateTime.fromMillisecondsSinceEpoch(map['timestamp'].toInt(),
+              isUtc: true), map['level']);
+
+  @override
+  String toString() => '$level[$timestamp]: $message';
 }
 
 class LogType {
diff --git a/lib/src/mouse.dart b/lib/src/mouse.dart
index 442d3fa..6ff42ff 100644
--- a/lib/src/mouse.dart
+++ b/lib/src/mouse.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Mouse extends _WebDriverBase {
@@ -16,10 +20,8 @@
     await _post('click', json);
   }
 
-  /**
-   * Click and hold any mouse button (at the coordinates set by the last
-   * moveTo command).
-   */
+  /// Click and hold any mouse button (at the coordinates set by the last
+  /// moveTo command).
   Future down([int button]) async {
     var json = {};
     if (button is num) {
@@ -28,10 +30,7 @@
     await _post('buttondown', json);
   }
 
-  /**
-   * Releases the mouse button previously held (where the mouse is currently
-   * at).
-   */
+  /// Releases the mouse button previously held (where the mouse is currently at).
   Future up([int button]) async {
     var json = {};
     if (button is num) {
@@ -45,20 +44,17 @@
     await _post('doubleclick');
   }
 
-  /**
-   * Move the mouse.
-   *
-   * If [element] is specified and [xOffset] and [yOffset] are not, will move
-   * the mouse to the center of the [element].
-   *
-   * If [xOffset] and [yOffset] are specified, will move the mouse that distance
-   * from its current location.
-   *
-   * If all three are specified, will move the mouse to the offset relative to
-   * the top-left corner of the [element].
-   *
-   * All other combinations of parameters are illegal.
-   */
+  /// Move the mouse.
+  ///
+  /// If [element] is specified and [xOffset] and [yOffset] are not, will move
+  /// the mouse to the center of the [element].
+  ///
+  /// If [xOffset] and [yOffset] are specified, will move the mouse that distance
+  /// from its current location.
+  ///
+  /// If all three are specified, will move the mouse to the offset relative to
+  /// the top-left corner of the [element].
+  /// All other combinations of parameters are illegal.
   Future moveTo({WebElement element, int xOffset, int yOffset}) async {
     var json = {};
     if (element is WebElement) {
@@ -70,4 +66,13 @@
     }
     await _post('moveto', json);
   }
+
+  @override
+  String toString() => '$driver.mouse';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is Mouse && other.driver == driver;
 }
diff --git a/lib/src/navigation.dart b/lib/src/navigation.dart
index 251aa8c..8a6e83f 100644
--- a/lib/src/navigation.dart
+++ b/lib/src/navigation.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Navigation extends _WebDriverBase {
@@ -17,4 +21,13 @@
   Future refresh() async {
     await _post('refresh');
   }
+
+  @override
+  String toString() => '$driver.navigate';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is Navigation && other.driver == driver;
 }
diff --git a/lib/src/options.dart b/lib/src/options.dart
index a2c1bf9..062c73e 100644
--- a/lib/src/options.dart
+++ b/lib/src/options.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Cookies extends _WebDriverBase {
@@ -34,7 +38,6 @@
 
     return controller.stream;
   }
-
 // TODO(DrMarcII): switch to this when async* is supported
 //  async* {
 //    var cookies = await _get('');
@@ -42,6 +45,15 @@
 //      yield new Cookie.fromJson(cookie);
 //    }
 //  }
+
+  @override
+  String toString() => '$driver.cookies';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is Cookies && other.driver == driver;
 }
 
 class Cookie {
@@ -90,6 +102,9 @@
     }
     return json;
   }
+
+  @override
+  String toString() => 'Cookie${toJson()}';
 }
 
 class Timeouts extends _WebDriverBase {
@@ -107,4 +122,13 @@
 
   /// Set the page load timeout.
   Future setPageLoadTimeout(Duration duration) => _set('page load', duration);
+
+  @override
+  String toString() => '$driver.timeouts';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is Timeouts && other.driver == driver;
 }
diff --git a/lib/src/target_locator.dart b/lib/src/target_locator.dart
index 02e5f32..33f3916 100644
--- a/lib/src/target_locator.dart
+++ b/lib/src/target_locator.dart
@@ -1,33 +1,30 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class TargetLocator extends _WebDriverBase {
   TargetLocator._(driver) : super(driver, '');
 
-  /**
-   * Change focus to another frame on the page.
-   *
-   * If [frame] is a:
-   *   int: select by its zero-based index
-   *   String: select frame by the name of the frame window or the id of the
-   *           frame or iframe tag.
-   *   WebElement: select the frrame for a previously found frame or iframe
-   *               element.
-   *   not provided: selects the first frame on the page or the main document.
-   *
-   * Throws [WebDriverError] no such frame if the specified frame can't be
-   * found.
-   */
+  /// Change focus to another frame on the page.
+  /// If [frame] is a:
+  ///   [int]: select by its zero-based index
+  ///   [String]: select frame by the name of the frame window or the id of the
+  ///           frame or iframe tag.
+  ///   [WebElement]: select the frame for a previously found frame or iframe
+  ///               element.
+  ///   not provided: selects the first frame on the page or the main document.
+  ///
+  ///   Throws [NoSuchFrameException] if the specified frame can't be found.
   Future frame([frame]) async {
     await _post('frame', {'id': frame});
   }
 
-  /**
-   * Switch the focus of future commands for this driver to the window with the
-   * given name/handle.
-   *
-   * Throws [WebDriverError] no such window if the specified window can't be
-   * found.
-   */
+  /// Switch the focus of future commands for this driver to the window with the
+  /// given name/handle.
+  ///
+  /// Throws [NoSuchWindowException] if the specified window can't be found.
   Future window(dynamic window) async {
     if (window is Window) {
       await _post('window', {'name': window.handle});
@@ -38,14 +35,21 @@
     }
   }
 
-  /**
-   * Switches to the currently active modal dialog for this particular driver
-   * instance.
-   *
-   * Throws WebDriverEror no alert present if there is not currently an alert.
-   */
+  /// Switches to the currently active modal dialog for this particular driver
+  /// instance.
+  ///
+  /// Throws [NoAlertPresentException] if there is not currently an alert.
   Future<Alert> get alert async {
     var text = await _get('alert_text');
     return new Alert._(text, driver);
   }
+
+  @override
+  String toString() => '$driver.switchTo';
+
+  @override
+  int get hashCode => driver.hashCode;
+
+  @override
+  bool operator ==(other) => other is TargetLocator && other.driver == driver;
 }
diff --git a/lib/src/util.dart b/lib/src/util.dart
deleted file mode 100644
index ac63a1d..0000000
--- a/lib/src/util.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-part of webdriver;
-
-const DEFAULT_TIMEOUT = const Duration(seconds: 5);
-const DEFAULT_INTERVAL = const Duration(milliseconds: 500);
-
-Future waitFor(Future predicate(), {Matcher matcher: isTrue,
-    Duration timeout: DEFAULT_TIMEOUT, Duration interval: DEFAULT_INTERVAL}) {
-  var endTime = new DateTime.now().add(timeout);
-  var function;
-  function = () async {
-    var value = await predicate();
-    try {
-      expect(value, matcher);
-      return value;
-    } catch (e) {
-      if (new DateTime.now().isAfter(endTime)) {
-        rethrow;
-      }
-    }
-    return await new Future.delayed(DEFAULT_INTERVAL, function);
-  };
-  return function();
-}
diff --git a/lib/src/web_driver.dart b/lib/src/web_driver.dart
index 984ff18..6ee0df2 100644
--- a/lib/src/web_driver.dart
+++ b/lib/src/web_driver.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class WebDriver implements SearchContext {
@@ -52,7 +56,7 @@
       var elements = await _post('elements', by);
       int i = 0;
       for (var element in elements) {
-        controller.add(new WebElement._(this, element['ELEMENT'], this, by, i));
+        controller.add(new WebElement._(this, element[_ELEMENT], this, by, i));
         i++;
       }
       await controller.close();
@@ -67,19 +71,17 @@
 //    int i = 0;
 //
 //    for (var element in elements) {
-//      yield new WebElement._(this, element['ELEMENT'], this, by, i);
+//      yield new WebElement._(this, element[_ELEMENT], this, by, i);
 //      i++;
 //    }
 //  }
 
-  /**
-   * Search for an element within the entire current page.
-   *
-   * Throws [WebDriverError] no such element if a matching element is not found.
-   */
+  /// Search for an element within the entire current page.
+  /// Throws [NoSuchElementException] if a matching element is not found.
+  @override
   Future<WebElement> findElement(By by) async {
     var element = await _post('element', by);
-    return new WebElement._(this, element['ELEMENT'], this, by);
+    return new WebElement._(this, element[_ELEMENT], this, by);
   }
 
   /// An artist's rendition of the current page's source.
@@ -101,10 +103,8 @@
 
     () async {
       var handles = await _get('window_handles');
-      int i = 0;
       for (var handle in handles) {
         controller.add(new Window._(this, handle));
-        i++;
       }
       await controller.close();
     }();
@@ -127,14 +127,12 @@
     return new Window._(this, handle);
   }
 
-  /**
-   *  The currently focused element, or the body element if no
-   *  element has focus.
-   */
+  /// The currently focused element, or the body element if no element has
+  /// focus.
   Future<WebElement> get activeElement async {
     var element = await _post('element/active');
     if (element != null) {
-      return new WebElement._(this, element['ELEMENT'], this, 'activeElement');
+      return new WebElement._(this, element[_ELEMENT], this, 'activeElement');
     }
     return null;
   }
@@ -157,52 +155,48 @@
   Future<List<int>> captureScreenshot() => _get('screenshot')
       .then((screenshot) => CryptoUtils.base64StringToBytes(screenshot));
 
-  /**
-   * Inject a snippet of JavaScript into the page for execution in the context
-   * of the currently selected frame. The executed script is assumed to be
-   * asynchronous and must signal that is done by invoking the provided
-   * callback, which is always provided as the final argument to the function.
-   * The value to this callback will be returned to the client.
-   *
-   * Asynchronous script commands may not span page loads. If an unload event
-   * is fired while waiting for a script result, an error will be thrown.
-   *
-   * The script argument defines the script to execute in the form of a
-   * function body. The function will be invoked with the provided args array
-   * and the values may be accessed via the arguments object in the order
-   * specified. The final argument will always be a callback function that must
-   * be invoked to signal that the script has finished.
-   *
-   * Arguments may be any JSON-able object. WebElements will be converted to
-   * the corresponding DOM element. Likewise, any DOM Elements in the script
-   * result will be converted to WebElements.
-   */
+  /// Inject a snippet of JavaScript into the page for execution in the context
+  /// of the currently selected frame. The executed script is assumed to be
+  /// asynchronous and must signal that is done by invoking the provided
+  /// callback, which is always provided as the final argument to the function.
+  /// The value to this callback will be returned to the client.
+  ///
+  /// Asynchronous script commands may not span page loads. If an unload event
+  /// is fired while waiting for a script result, an error will be thrown.
+  ///
+  /// The script argument defines the script to execute in the form of a
+  /// function body. The function will be invoked with the provided args array
+  /// and the values may be accessed via the arguments object in the order
+  /// specified. The final argument will always be a callback function that must
+  /// be invoked to signal that the script has finished.
+  ///
+  /// Arguments may be any JSON-able object. WebElements will be converted to
+  /// the corresponding DOM element. Likewise, any DOM Elements in the script
+  /// result will be converted to WebElements.
   Future executeAsync(String script, List args) => _post('execute_async', {
     'script': script,
     'args': args
   }).then(_recursiveElementify);
 
-  /**
-   * Inject a snippet of JavaScript into the page for execution in the context
-   * of the currently selected frame. The executed script is assumed to be
-   * synchronous and the result of evaluating the script is returned.
-   *
-   * The script argument defines the script to execute in the form of a
-   * function body. The value returned by that function will be returned to the
-   * client. The function will be invoked with the provided args array and the
-   * values may be accessed via the arguments object in the order specified.
-   *
-   * Arguments may be any JSON-able object. WebElements will be converted to
-   * the corresponding DOM element. Likewise, any DOM Elements in the script
-   * result will be converted to WebElements.
-   */
+  /// Inject a snippet of JavaScript into the page for execution in the context
+  /// of the currently selected frame. The executed script is assumed to be
+  /// synchronous and the result of evaluating the script is returned.
+  ///
+  /// The script argument defines the script to execute in the form of a
+  /// function body. The value returned by that function will be returned to the
+  /// client. The function will be invoked with the provided args array and the
+  /// values may be accessed via the arguments object in the order specified.
+  ///
+  /// Arguments may be any JSON-able object. WebElements will be converted to
+  /// the corresponding DOM element. Likewise, any DOM Elements in the script
+  /// result will be converted to WebElements.
   Future execute(String script, List args) => _post(
       'execute', {'script': script, 'args': args}).then(_recursiveElementify);
 
   dynamic _recursiveElementify(result) {
     if (result is Map) {
       if (result.length == 1 && result.containsKey(_ELEMENT)) {
-        return new WebElement._(this, result['ELEMENT'], this, 'javascript');
+        return new WebElement._(this, result[_ELEMENT], this, 'javascript');
       } else {
         var newResult = {};
         result.forEach((key, value) {
@@ -225,4 +219,10 @@
 
   Future _delete(String command) =>
       _commandProcessor.delete(_prefix.resolve(command));
+
+  @override
+  WebDriver get driver => this;
+
+  @override
+  String toString() => 'WebDriver($_prefix)';
 }
diff --git a/lib/src/web_element.dart b/lib/src/web_element.dart
index 3798b3a..45a3c05 100644
--- a/lib/src/web_element.dart
+++ b/lib/src/web_element.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class WebElement extends _WebDriverBase implements SearchContext {
@@ -59,14 +63,12 @@
   ///  Visible text within this element.
   Future<String> get text => _get('text');
 
-  /**
-   * Find an element nested within this element.
-   *
-   * Throws [WebDriverError] no such element if matching element is not found.
-   */
+  ///Find an element nested within this element.
+  ///
+  /// Throws [NoSuchElementException] if matching element is not found.
   Future<WebElement> findElement(By by) async {
     var element = await _post('element', by);
-    return new WebElement._(driver, element['ELEMENT'], this, by);
+    return new WebElement._(driver, element[_ELEMENT], this, by);
   }
 
   /// Find multiple elements nested within this element.
@@ -78,7 +80,7 @@
       int i = 0;
       for (var element in elements) {
         controller
-            .add(new WebElement._(driver, element['ELEMENT'], this, by, i));
+            .add(new WebElement._(driver, element[_ELEMENT], this, by, i));
         i++;
       }
       await controller.close();
@@ -92,31 +94,58 @@
 //    var elements = await _post('elements', by);
 //    int i = 0;
 //    for (var element in elements) {
-//      yield new WebElement._(driver, element['ELEMENT'], this, by, i);
+//      yield new WebElement._(driver, element[_ELEMENT], this, by, i);
 //      i++;
 //    }
 //  }
 
-  /**
-   * Access to the HTML attributes of this tag.
-   *
-   * TODO(DrMarcII): consider special handling of boolean attributes.
-   */
+  /// Access to the HTML attributes of this tag.
+  ///
+  /// TODO(DrMarcII): consider special handling of boolean attributes.
   Attributes get attributes => new Attributes._(driver, '$_prefix/attribute');
 
-  /**
-   * Access to the cssProperties of this element.
-   *
-   * TODO(DrMarcII): consider special handling of color and possibly other
-   *                 properties.
-   */
+  /// Access to the cssProperties of this element.
+  ///
+  /// TODO(DrMarcII): consider special handling of color and possibly other
+  /// properties.
   Attributes get cssProperties => new Attributes._(driver, '$_prefix/css');
 
-  /**
-   * Does this element represent the same element as another element?
-   * Not the same as ==
-   */
+  /// Does this element represent the same element as another element?
+  /// Not the same as ==
   Future<bool> equals(WebElement other) => _get('equals/${other.id}');
 
-  Map<String, String> toJson() => {'ELEMENT': id};
+  Map<String, String> toJson() => {_ELEMENT: id};
+
+  @override
+  int get hashCode => driver.hashCode * 3 + id.hashCode;
+
+  @override
+  bool operator ==(other) =>
+      other is WebElement && other.driver == this.driver && other.id == this.id;
+
+  @override
+  String toString() {
+    var out = new StringBuffer()..write(context);
+    if (locator is By) {
+      if (index == null) {
+        out..write('.findElement(');
+      } else {
+        out..write('.findElements(');
+      }
+      out
+        ..write(locator)
+        ..write(')');
+    } else {
+      out
+        ..write('.')
+        ..write(locator);
+    }
+    if (index != null) {
+      out
+        ..write('[')
+        ..write(index)
+        ..write(']');
+    }
+    return out.toString();
+  }
 }
diff --git a/lib/src/window.dart b/lib/src/window.dart
index 90fac7f..c8f750c 100644
--- a/lib/src/window.dart
+++ b/lib/src/window.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 part of webdriver;
 
 class Window extends _WebDriverBase {
@@ -41,4 +45,7 @@
   bool operator ==(other) => other is Window &&
       other.driver == this.driver &&
       other.handle == this.handle;
+
+  @override
+  String toString() => '$driver.windows[$handle]';
 }
diff --git a/lib/webdriver.dart b/lib/webdriver.dart
index 07177b2..ccfaada 100644
--- a/lib/webdriver.dart
+++ b/lib/webdriver.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -12,7 +12,8 @@
 import 'package:crypto/crypto.dart';
 import 'package:matcher/matcher.dart';
 
-import 'src/lock.dart';
+import 'async_helpers.dart';
+export 'async_helpers.dart' show waitFor;
 
 part 'src/alert.dart';
 part 'src/capabilities.dart';
@@ -29,4 +30,3 @@
 part 'src/web_driver.dart';
 part 'src/web_element.dart';
 part 'src/window.dart';
-part 'src/util.dart';
diff --git a/pubspec.yaml b/pubspec.yaml
index 95d1917..71d304a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,5 +11,5 @@
   crypto: '^0.9.0'
   matcher: '^0.11.4'
 dev_dependencies:
-  path: '^1.3.1'
+  path: '^1.3.2'
   unittest: '^0.11.5'
diff --git a/test/async_helpers_test.dart b/test/async_helpers_test.dart
new file mode 100644
index 0000000..23b6fd2
--- /dev/null
+++ b/test/async_helpers_test.dart
@@ -0,0 +1,191 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library webdriver_test.async_helpers;
+
+import 'dart:async';
+
+import 'package:unittest/unittest.dart';
+import 'package:webdriver/async_helpers.dart';
+
+void main() {
+  group('Lock', () {
+    test('basic acquire/release', () async {
+      var lock = new Lock();
+      expect(lock.isHeld, isFalse);
+      await lock.acquire();
+      expect(lock.isHeld, isTrue);
+      lock.release();
+      expect(lock.isHeld, isFalse);
+      await lock.acquire();
+      expect(lock.isHeld, isTrue);
+      lock.release();
+    });
+
+    test('release without acquiring fails', () {
+      var lock = new Lock();
+      expect(() => lock.release(), throwsA(new isInstanceOf<StateError>()));
+    });
+
+    test('locking prevents acquisition of lock', () async {
+      var lock = new Lock();
+      var secondLockAcquired = false;
+      await lock.acquire();
+      lock.acquire().then((_) => secondLockAcquired = true);
+      // Make sure that lock is not unacquired just because of timing
+      await new Future.delayed(const Duration(seconds: 1));
+      expect(secondLockAcquired, isFalse);
+      lock.release();
+      // Make sure that enough time has occurred that lock is acquired
+      await new Future.delayed(const Duration(seconds: 1));
+      expect(secondLockAcquired, isTrue);
+    });
+  });
+
+  group('Clock.waitFor', () {
+    var clock = new FakeClock();
+
+    test('that returns a string', () async {
+      var count = 0;
+      var result = await clock.waitFor(() {
+        if (count == 2) return 'webdriver - Google Search';
+        count++;
+        return count;
+      }, matcher: equals('webdriver - Google Search'));
+
+      expect(result, equals('webdriver - Google Search'));
+    });
+
+    test('that returns null', () async {
+      var count = 0;
+      var result = await clock.waitFor(() {
+        if (count == 2) return null;
+        count++;
+        return count;
+      }, matcher: isNull);
+      expect(result, isNull);
+    });
+
+    test('that returns false', () async {
+      var count = 0;
+      var result = await clock.waitFor(() {
+        if (count == 2) return false;
+        count++;
+        return count;
+      }, matcher: isFalse);
+      expect(result, isFalse);
+    });
+
+    test('that returns a string, default matcher', () async {
+      var count = 0;
+      var result = await clock.waitFor(() {
+        if (count == 2) return 'Google';
+        count++;
+        return null;
+      });
+      expect(result, equals('Google'));
+    });
+
+    test('throws before successful', () async {
+      var count = 0;
+      var result = await clock.waitFor(() {
+        expect(count, lessThanOrEqualTo(2));
+        if (count == 2) {
+          count++;
+          return false;
+        }
+        count++;
+        return null;
+      });
+      expect(result, isFalse);
+    });
+
+    test('throws if condition throws and timeouts', () async {
+      var exception;
+
+      try {
+        await clock.waitFor(() => throw 'an exception');
+      } catch (e) {
+        exception = e;
+      }
+      expect(exception, 'an exception');
+    });
+
+    test('throws if condition never matches', () async {
+      var exception;
+      try {
+        await clock.waitFor(() => null);
+      } catch (e) {
+        exception = e;
+      }
+      expect(exception, isNotNull);
+    });
+
+    test('uses Future value', () async {
+      var result = await clock.waitFor(() => new Future.value('a value'),
+          matcher: 'a value');
+      expect(result, 'a value');
+    });
+
+    test('works with Future exceptions', () async {
+      var exception;
+
+      try {
+        await clock.waitFor(() => new Future.error('an exception'));
+      } catch (e) {
+        exception = e;
+      }
+      expect(exception, 'an exception');
+    });
+
+    test('sanity test with real Clock -- successful', () async {
+      var clock = new Clock();
+      var count = 0;
+      var result = await clock.waitFor(() {
+        if (count < 2) {
+          count++;
+          return null;
+        } else {
+          return 'a value';
+        }
+      });
+      expect(result, 'a value');
+    });
+
+    test('sanity test with real Clock -- throws', () async {
+      var clock = new Clock();
+      var exception;
+      try {
+        await clock.waitFor(() => throw 'an exception');
+      } catch (e) {
+        exception = e;
+      }
+      expect(exception, 'an exception');
+    });
+
+    test('sanity test with real Clock -- never matches', () async {
+      var clock = new Clock();
+      var exception;
+      try {
+        await clock.waitFor(() => null);
+      } catch (e) {
+        exception = e;
+      }
+      expect(exception, isNotNull);
+    });
+  });
+}
+
+/// FakeClock for testing waitFor functionality.
+class FakeClock extends Clock {
+  var _now = new DateTime(2020);
+
+  @override
+  DateTime get now => _now;
+
+  Future sleep([Duration interval = defaultInterval]) {
+    _now = _now.add(interval);
+    return new Future.value();
+  }
+}
diff --git a/test/src/alert_test.dart b/test/src/alert_test.dart
index 345e807..60fa10f 100644
--- a/test/src/alert_test.dart
+++ b/test/src/alert_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.alert;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/keyboard_test.dart b/test/src/keyboard_test.dart
index 6397490..d67a2d1 100644
--- a/test/src/keyboard_test.dart
+++ b/test/src/keyboard_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.keyboard;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/lock_test.dart b/test/src/lock_test.dart
deleted file mode 100644
index 0653b6a..0000000
--- a/test/src/lock_test.dart
+++ /dev/null
@@ -1,41 +0,0 @@
-library webdriver.lock_test;
-
-import 'dart:async';
-
-import 'package:unittest/unittest.dart';
-import 'package:webdriver/src/lock.dart';
-
-void main() {
-  group('Lock', () {
-    test('basic acquire/release', () async {
-      var lock = new Lock();
-      expect(lock.isAcquired, isFalse);
-      await lock.acquire();
-      expect(lock.isAcquired, isTrue);
-      lock.release();
-      expect(lock.isAcquired, isFalse);
-      await lock.acquire();
-      expect(lock.isAcquired, isTrue);
-      lock.release();
-    });
-
-    test('release without acquiring fails', () {
-      var lock = new Lock();
-      expect(() => lock.release(), throwsA(new isInstanceOf<StateError>()));
-    });
-
-    test('locking prevents acquisition of lock', () async {
-      var lock = new Lock();
-      var secondLockAcquired = false;
-      await lock.acquire();
-      lock.acquire().then((_) => secondLockAcquired = true);
-      // Make sure that lock is not unacquired just because of timing
-      await new Future.delayed(const Duration(seconds: 1));
-      expect(secondLockAcquired, isFalse);
-      lock.release();
-      // Make sure that enough time has occurred that lock is acquired
-      await new Future.delayed(const Duration(seconds: 1));
-      expect(secondLockAcquired, isTrue);
-    });
-  });
-}
diff --git a/test/src/logs_test.dart b/test/src/logs_test.dart
index e09ffe3..b497504 100644
--- a/test/src/logs_test.dart
+++ b/test/src/logs_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.logs;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/mouse_test.dart b/test/src/mouse_test.dart
index 621f4da..425c41a 100644
--- a/test/src/mouse_test.dart
+++ b/test/src/mouse_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.mouse;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/navigation_test.dart b/test/src/navigation_test.dart
index f6fbfe7..a3ef30e 100644
--- a/test/src/navigation_test.dart
+++ b/test/src/navigation_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.navigation;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/options_test.dart b/test/src/options_test.dart
index ba9af04..093ad7c 100644
--- a/test/src/options_test.dart
+++ b/test/src/options_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.options;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/target_locator_test.dart b/test/src/target_locator_test.dart
index 63bac73..a0c96da 100644
--- a/test/src/target_locator_test.dart
+++ b/test/src/target_locator_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.target_locator;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/web_driver_test.dart b/test/src/web_driver_test.dart
index d0b0c29..ae08a78 100644
--- a/test/src/web_driver_test.dart
+++ b/test/src/web_driver_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.web_driver;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/web_element_test.dart b/test/src/web_element_test.dart
index fa17209..b7c24f9 100644
--- a/test/src/web_element_test.dart
+++ b/test/src/web_element_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.web_element;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/src/window_test.dart b/test/src/window_test.dart
index 448cde4..550413a 100644
--- a/test/src/window_test.dart
+++ b/test/src/window_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test.window;
 
 import 'package:unittest/unittest.dart';
diff --git a/test/test_util.dart b/test/test_util.dart
index cf3dd7d..bcbe7bb 100644
--- a/test/test_util.dart
+++ b/test/test_util.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test_util;
 
 import 'dart:async';
@@ -8,7 +12,6 @@
 import 'package:unittest/unittest.dart';
 import 'package:webdriver/webdriver.dart';
 
-final Matcher isWebDriverError = new isInstanceOf<WebDriverError>();
 final Matcher isWebElement = new isInstanceOf<WebElement>();
 final Matcher isSize = new isInstanceOf<Size>();
 final Matcher isPoint = new isInstanceOf<Point>();
diff --git a/test/webdriver_test.dart b/test/webdriver_test.dart
index 8b86c67..f998da9 100644
--- a/test/webdriver_test.dart
+++ b/test/webdriver_test.dart
@@ -1,8 +1,12 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 library webdriver_test;
 
+import 'async_helpers_test.dart' as async_helpers;
 import 'src/alert_test.dart' as alert;
 import 'src/keyboard_test.dart' as keyboard;
-import 'src/lock_test.dart' as lock;
 import 'src/logs_test.dart' as logs;
 import 'src/mouse_test.dart' as mouse;
 import 'src/navigation_test.dart' as navigation;
@@ -17,9 +21,9 @@
  * as they are slow and they have external dependencies.
  */
 void main() {
+  async_helpers.main();
   alert.main();
   keyboard.main();
-  lock.main();
   logs.main();
   mouse.main();
   navigation.main();