Merge branch 'master' of github.com:google/webdriver.dart
diff --git a/lib/async_core.dart b/lib/async_core.dart
index 8013467..28af2ae 100644
--- a/lib/async_core.dart
+++ b/lib/async_core.dart
@@ -91,7 +91,7 @@
   final handler = getHandler(spec);
 
   final session = await client.send(handler.session.buildInfoRequest(sessionId),
-      handler.session.parseInfoResponse);
+      (response) => handler.session.parseInfoResponse(response, sessionId));
 
   if (session.spec != WebDriverSpec.JsonWire &&
       session.spec != WebDriverSpec.W3c) {
diff --git a/lib/async_html.dart b/lib/async_html.dart
index c3740ff..ec38faf 100644
--- a/lib/async_html.dart
+++ b/lib/async_html.dart
@@ -36,9 +36,13 @@
 Future<core.WebDriver> createDriver(
         {Uri uri,
         Map<String, dynamic> desired,
-        WebDriverSpec spec = WebDriverSpec.Auto}) =>
-    core.createDriver((prefix) => AsyncXhrRequestClient(prefix),
-        uri: uri, desired: desired, spec: spec);
+        WebDriverSpec spec = WebDriverSpec.Auto,
+        Map<String, String> webDriverHeaders = const {}}) =>
+    core.createDriver(
+        (prefix) => AsyncXhrRequestClient(prefix, headers: webDriverHeaders),
+        uri: uri,
+        desired: desired,
+        spec: spec);
 
 /// Creates an async WebDriver from existing session using
 /// [AsyncXhrRequestClient].
diff --git a/lib/async_io.dart b/lib/async_io.dart
index b88ad3e..a2f0508 100644
--- a/lib/async_io.dart
+++ b/lib/async_io.dart
@@ -38,9 +38,13 @@
 Future<core.WebDriver> createDriver(
         {Uri uri,
         Map<String, dynamic> desired,
-        core.WebDriverSpec spec = core.WebDriverSpec.Auto}) =>
-    core.createDriver((prefix) => AsyncIoRequestClient(prefix),
-        uri: uri, desired: desired, spec: spec);
+        core.WebDriverSpec spec = core.WebDriverSpec.Auto,
+        Map<String, String> webDriverHeaders = const {}}) =>
+    core.createDriver(
+        (prefix) => AsyncIoRequestClient(prefix, headers: webDriverHeaders),
+        uri: uri,
+        desired: desired,
+        spec: spec);
 
 /// Creates an async WebDriver from existing session using
 /// [AsyncIoRequestClient].
diff --git a/lib/src/async/logs.dart b/lib/src/async/logs.dart
index 58fa3ca..3611d14 100644
--- a/lib/src/async/logs.dart
+++ b/lib/src/async/logs.dart
@@ -14,6 +14,7 @@
 
 import 'dart:async';
 
+import 'package:webdriver/src/common/exception.dart';
 import 'package:webdriver/src/common/log.dart';
 import 'package:webdriver/src/common/request_client.dart';
 import 'package:webdriver/src/common/webdriver_handler.dart';
@@ -32,8 +33,8 @@
       for (var entry in entries) {
         yield entry;
       }
-    } on UnsupportedError {
-      // Produces no entries for W3C/Firefox.
+    } on UnknownCommandException {
+      // Produces no entries for Firefox.
     }
   }
 
diff --git a/lib/src/async/mouse.dart b/lib/src/async/mouse.dart
index 4529e49..67d3882 100644
--- a/lib/src/async/mouse.dart
+++ b/lib/src/async/mouse.dart
@@ -18,6 +18,8 @@
 import 'package:webdriver/src/common/mouse.dart';
 import 'package:webdriver/src/common/request_client.dart';
 import 'package:webdriver/src/common/webdriver_handler.dart';
+import 'package:webdriver/src/handler/json_wire_handler.dart';
+import 'package:webdriver/src/handler/w3c_handler.dart';
 
 class Mouse {
   final AsyncRequestClient _client;
@@ -53,8 +55,12 @@
   /// 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].
+  /// If all three are specified, the behavior will be different
+  /// for W3C and JsonWire. For W3C, it will use [element] center as the
+  /// origin, while for JsonWire, it will use [element] top left corner.
+  /// To get a consistent behavior across browsers, you can try
+  /// [moveToElementCenter] and [moveToElementTopLeft] to specify the origin you
+  /// would like to use.
   ///
   /// All other combinations of parameters are illegal.
   ///
@@ -73,6 +79,47 @@
               absolute: absolute),
           _handler.mouse.parseMoveToResponse);
 
+  /// Moves to [element], with an offset of [xOffset] and [yOffset] based on the
+  /// center of [element].
+  Future<void> moveToElementCenter(WebElement element,
+      {int xOffset, int yOffset}) async {
+    if (_handler is JsonWireWebDriverHandler) {
+      final size = await element.size;
+      await moveTo(
+          element: element,
+          xOffset: (xOffset ?? 0) + size.width ~/ 2,
+          yOffset: (yOffset ?? 0) + size.height ~/ 2);
+    } else {
+      await moveTo(element: element, xOffset: xOffset, yOffset: yOffset);
+    }
+  }
+
+  /// Moves to [element], with an offset of [xOffset] and [yOffset] based on the
+  /// top left corner of [element].
+  Future<void> moveToElementTopLeft(WebElement element,
+      {int xOffset, int yOffset}) async {
+    if (_handler is W3cWebDriverHandler) {
+      final size = await element.size;
+      await moveTo(
+          element: element,
+          xOffset: (xOffset ?? 0) - size.width ~/ 2,
+          yOffset: (yOffset ?? 0) - size.height ~/ 2);
+    } else {
+      await moveTo(element: element, xOffset: xOffset, yOffset: yOffset);
+    }
+  }
+
+  /// Moves the mouse away to hide its effect, like hover over element.
+  ///
+  /// For W3C, the mouse cannot move out of the screen, the workaround would be
+  /// to move to somewhere on edge where it's not on any element. You can
+  /// configure the location with [w3cXOffset] and [w3cYOffset]. By default,
+  /// it's at (1000, 0).
+  Future<void> hide({int w3cXOffset = 1000, int w3cYOffset = 0}) =>
+      _handler is W3cWebDriverHandler
+          ? moveTo(xOffset: w3cXOffset, yOffset: w3cYOffset, absolute: true)
+          : moveTo(xOffset: -10000, yOffset: -10000);
+
   @override
   String toString() => '$_handler.mouse($_client)';
 
diff --git a/lib/src/async/web_driver.dart b/lib/src/async/web_driver.dart
index 45a5fb6..460e14f 100644
--- a/lib/src/async/web_driver.dart
+++ b/lib/src/async/web_driver.dart
@@ -194,12 +194,24 @@
       _handler.core.buildScreenshotRequest(),
       _handler.core.parseScreenshotResponse);
 
+  /// Take a screenshot of the specified element as PNG and return it as
+  /// base64-encoded string.
+  Future<String> captureElementScreenshotAsBase64(WebElement element) =>
+      _client.send(_handler.core.buildElementScreenshotRequest(element.id),
+          _handler.core.parseScreenshotResponse);
+
   /// Take a screenshot of the current page as PNG as list of uint8.
   Future<List<int>> captureScreenshotAsList() async {
     var base64Encoded = captureScreenshotAsBase64();
     return base64.decode(await base64Encoded);
   }
 
+  /// Take a screenshot of the specified element as PNG as list of uint8.
+  Future<List<int>> captureElementScreenshotAsList(WebElement element) async {
+    var base64Encoded = captureElementScreenshotAsBase64(element);
+    return base64.decode(await base64Encoded);
+  }
+
   /// Take a screenshot of the current page as PNG as stream of uint8.
   ///
   /// Don't use this method. Prefer [captureScreenshotAsBase64] or
diff --git a/lib/src/async/web_element.dart b/lib/src/async/web_element.dart
index 198b3cc..d634dba 100644
--- a/lib/src/async/web_element.dart
+++ b/lib/src/async/web_element.dart
@@ -84,6 +84,13 @@
       _handler.element.buildSizeRequest(id),
       _handler.element.parseSizeResponse);
 
+  /// The bounds of this element.
+  Future<Rectangle<int>> get rect async {
+    final location = await this.location;
+    final size = await this.size;
+    return Rectangle<int>(location.x, location.y, size.width, size.height);
+  }
+
   /// The tag name for this element.
   Future<String> get name => _client.send(_handler.element.buildNameRequest(id),
       _handler.element.parseNameResponse);
diff --git a/lib/src/common/web_element.dart b/lib/src/common/web_element.dart
index 452f78d..3eb8b5e 100644
--- a/lib/src/common/web_element.dart
+++ b/lib/src/common/web_element.dart
@@ -1,6 +1,9 @@
+import '../handler/json_wire/utils.dart' show jsonWireElementStr;
+import '../handler/w3c/utils.dart' show w3cElementStr;
+
 /// Common interface for web element, containing only the element id.
 abstract class WebElement {
   String get id;
 
-  Map<String, String> toJson() => {'ELEMENT': id};
+  Map<String, String> toJson() => {jsonWireElementStr: id, w3cElementStr: id};
 }
diff --git a/lib/src/common/webdriver_handler.dart b/lib/src/common/webdriver_handler.dart
index 44901e9..a2ffa04 100644
--- a/lib/src/common/webdriver_handler.dart
+++ b/lib/src/common/webdriver_handler.dart
@@ -1,5 +1,6 @@
 import 'dart:math';
 
+import 'package:webdriver/async_core.dart';
 import 'package:webdriver/src/common/cookie.dart';
 import 'package:webdriver/src/common/log.dart';
 import 'package:webdriver/src/common/mouse.dart';
@@ -66,7 +67,7 @@
   WebDriverRequest buildInfoRequest(String id);
 
   /// Parses response for 'Get Session Info'.
-  SessionInfo parseInfoResponse(WebDriverResponse response);
+  SessionInfo parseInfoResponse(WebDriverResponse response, [String sessionId]);
 }
 
 abstract class CoreHandler {
@@ -91,6 +92,9 @@
   /// Builds request for 'Take Screenshot'.
   WebDriverRequest buildScreenshotRequest();
 
+  /// Builds request for 'Take Screenshot of Element'.
+  WebDriverRequest buildElementScreenshotRequest(String elementId);
+
   /// Parses response for 'Take Screenshot' to get a base64 encoded image.
   String parseScreenshotResponse(WebDriverResponse response);
 
diff --git a/lib/src/handler/infer_handler.dart b/lib/src/handler/infer_handler.dart
index ad81379..abc895f 100644
--- a/lib/src/handler/infer_handler.dart
+++ b/lib/src/handler/infer_handler.dart
@@ -112,7 +112,8 @@
       WebDriverRequest.getRequest('session/$id');
 
   @override
-  SessionInfo parseInfoResponse(WebDriverResponse response) {
+  SessionInfo parseInfoResponse(WebDriverResponse response,
+      [String sessionId]) {
     if (response.statusCode == 404) {
       // May be W3C, as it will throw an unknown command exception.
       Map body;
@@ -135,9 +136,6 @@
             'produced by W3C WebDriver): ${response.body}');
       }
 
-      final sessionId = RegExp(r'/session/([0-9a-f-]*) ')
-          .firstMatch(body['message'] as String)
-          .group(1);
       return SessionInfo(sessionId, WebDriverSpec.W3c, Capabilities.empty);
     }
 
diff --git a/lib/src/handler/json_wire/core.dart b/lib/src/handler/json_wire/core.dart
index 3485e65..4de1af5 100644
--- a/lib/src/handler/json_wire/core.dart
+++ b/lib/src/handler/json_wire/core.dart
@@ -31,6 +31,10 @@
       WebDriverRequest.getRequest('screenshot');
 
   @override
+  WebDriverRequest buildElementScreenshotRequest(String elementId) =>
+      new WebDriverRequest.getRequest('${elementPrefix(elementId)}screenshot');
+
+  @override
   String parseScreenshotResponse(WebDriverResponse response) =>
       parseJsonWireResponse(response);
 
diff --git a/lib/src/handler/json_wire/element_finder.dart b/lib/src/handler/json_wire/element_finder.dart
index 722dc3f..c1ac3a4 100644
--- a/lib/src/handler/json_wire/element_finder.dart
+++ b/lib/src/handler/json_wire/element_finder.dart
@@ -44,6 +44,6 @@
 
   @override
   String parseFindElementResponse(WebDriverResponse response) {
-    return parseJsonWireResponse(response)[jsonWireElementStr];
+    return (parseJsonWireResponse(response) ?? {})[jsonWireElementStr];
   }
 }
diff --git a/lib/src/handler/json_wire/session.dart b/lib/src/handler/json_wire/session.dart
index 3b043f4..4d358d7 100644
--- a/lib/src/handler/json_wire/session.dart
+++ b/lib/src/handler/json_wire/session.dart
@@ -22,7 +22,8 @@
       WebDriverRequest.getRequest('session/$id');
 
   @override
-  SessionInfo parseInfoResponse(WebDriverResponse response) {
+  SessionInfo parseInfoResponse(WebDriverResponse response,
+      [String sessionId]) {
     final session = parseJsonWireResponse(response, valueOnly: false);
     return SessionInfo(
         session['sessionId'], WebDriverSpec.JsonWire, session['value']);
diff --git a/lib/src/handler/w3c/core.dart b/lib/src/handler/w3c/core.dart
index 94ed27e..bf852e8 100644
--- a/lib/src/handler/w3c/core.dart
+++ b/lib/src/handler/w3c/core.dart
@@ -31,6 +31,10 @@
       WebDriverRequest.getRequest('screenshot');
 
   @override
+  WebDriverRequest buildElementScreenshotRequest(String elementId) =>
+      new WebDriverRequest.getRequest('${elementPrefix(elementId)}screenshot');
+
+  @override
   String parseScreenshotResponse(WebDriverResponse response) =>
       parseW3cResponse(response);
 
diff --git a/lib/src/handler/w3c/element.dart b/lib/src/handler/w3c/element.dart
index ff0212b..178a699 100644
--- a/lib/src/handler/w3c/element.dart
+++ b/lib/src/handler/w3c/element.dart
@@ -61,12 +61,12 @@
 
   @override
   WebDriverRequest buildDisplayedRequest(String elementId) {
-    return buildCssPropertyRequest(elementId, 'display');
+    return WebDriverRequest.getRequest('${elementPrefix(elementId)}displayed');
   }
 
   @override
   bool parseDisplayedResponse(WebDriverResponse response) {
-    return parseCssPropertyResponse(response) != 'none';
+    return parseW3cResponse(response);
   }
 
   @override
diff --git a/lib/src/handler/w3c/element_finder.dart b/lib/src/handler/w3c/element_finder.dart
index eebaaf8..8e236bb 100644
--- a/lib/src/handler/w3c/element_finder.dart
+++ b/lib/src/handler/w3c/element_finder.dart
@@ -25,6 +25,10 @@
         using = 'css selector';
         value = by.value;
         break;
+      case 'class name': // This doesn't exist in the W3C spec.
+        using = 'css selector';
+        value = '.${by.value}';
+        break;
       // xpath, css selector, link text, partial link text, seem fine.
       default:
         using = by.using;
@@ -65,6 +69,6 @@
 
   @override
   String parseFindElementResponse(WebDriverResponse response) {
-    return parseW3cResponse(response)[w3cElementStr];
+    return (parseW3cResponse(response) ?? {})[w3cElementStr];
   }
 }
diff --git a/lib/src/handler/w3c/session.dart b/lib/src/handler/w3c/session.dart
index d72c567..6a96261 100644
--- a/lib/src/handler/w3c/session.dart
+++ b/lib/src/handler/w3c/session.dart
@@ -27,6 +27,7 @@
       WebDriverRequest.nullRequest(id);
 
   @override
-  SessionInfo parseInfoResponse(WebDriverResponse response) =>
+  SessionInfo parseInfoResponse(WebDriverResponse response,
+          [String sessionId]) =>
       SessionInfo(response.body, WebDriverSpec.W3c, Capabilities.empty);
 }
diff --git a/lib/src/handler/w3c_handler.dart b/lib/src/handler/w3c_handler.dart
index 932a2b0..913a778 100644
--- a/lib/src/handler/w3c_handler.dart
+++ b/lib/src/handler/w3c_handler.dart
@@ -1,5 +1,6 @@
 import 'dart:convert';
 
+import 'package:webdriver/src/common/log.dart';
 import 'package:webdriver/src/common/request.dart';
 import 'package:webdriver/src/common/webdriver_handler.dart';
 import 'package:webdriver/src/handler/w3c/alert.dart';
@@ -54,8 +55,7 @@
   TimeoutsHandler timeouts = W3cTimeoutsHandler();
 
   @override
-  LogsHandler get logs =>
-      throw UnsupportedError('Unsupported for W3cWebDriverHandler');
+  LogsHandler get logs => W3cLogsHandler();
 
   @override
   WebDriverRequest buildGeneralRequest(HttpMethod method, String uri,
@@ -71,3 +71,15 @@
   @override
   String toString() => 'W3C';
 }
+
+class W3cLogsHandler extends LogsHandler {
+  @override
+  WebDriverRequest buildGetLogsRequest(String logType) =>
+      WebDriverRequest.postRequest('log', {'type': logType});
+
+  @override
+  List<LogEntry> parseGetLogsResponse(WebDriverResponse response) =>
+      parseW3cResponse(response)
+          .map<LogEntry>((e) => LogEntry.fromMap(e))
+          .toList();
+}
diff --git a/lib/src/request/async_io_request_client.dart b/lib/src/request/async_io_request_client.dart
index 8e0cc08..73a6109 100644
--- a/lib/src/request/async_io_request_client.dart
+++ b/lib/src/request/async_io_request_client.dart
@@ -10,10 +10,13 @@
 /// Async request client using dart:io package.
 class AsyncIoRequestClient extends AsyncRequestClient {
   final HttpClient client = HttpClient();
+  final Map<String, String> _headers;
 
   final Lock _lock = Lock();
 
-  AsyncIoRequestClient(Uri prefix) : super(prefix);
+  AsyncIoRequestClient(Uri prefix, {Map<String, String> headers = const {}})
+      : _headers = headers,
+        super(prefix);
 
   @override
   Future<WebDriverResponse> sendRaw(WebDriverRequest request) async {
@@ -33,6 +36,7 @@
     }
 
     httpRequest.followRedirects = true;
+    _headers.forEach(httpRequest.headers.add);
     httpRequest.headers.add(HttpHeaders.acceptHeader, 'application/json');
     httpRequest.headers.add(HttpHeaders.acceptCharsetHeader, utf8.name);
     httpRequest.headers.add(HttpHeaders.cacheControlHeader, 'no-cache');
diff --git a/lib/src/request/async_xhr_request_client.dart b/lib/src/request/async_xhr_request_client.dart
index e898546..8eceda6 100644
--- a/lib/src/request/async_xhr_request_client.dart
+++ b/lib/src/request/async_xhr_request_client.dart
@@ -11,8 +11,11 @@
 /// On the low level, it's using XMLHttpRequest object (XHR).
 class AsyncXhrRequestClient extends AsyncRequestClient {
   final Lock _lock = Lock();
+  final Map<String, String> _headers;
 
-  AsyncXhrRequestClient(Uri prefix) : super(prefix);
+  AsyncXhrRequestClient(Uri prefix, {Map<String, String> headers = const {}})
+      : _headers = headers,
+        super(prefix);
 
   @override
   Future<WebDriverResponse> sendRaw(WebDriverRequest request) async {
@@ -20,6 +23,7 @@
 
     final headers = {
       'Accept': 'application/json',
+      ..._headers,
     };
 
     HttpRequest httpRequest;
diff --git a/lib/src/request/sync_http_request_client.dart b/lib/src/request/sync_http_request_client.dart
index 8676eb9..e9dc718 100644
--- a/lib/src/request/sync_http_request_client.dart
+++ b/lib/src/request/sync_http_request_client.dart
@@ -7,7 +7,10 @@
 
 /// Sync request client using sync_http package.
 class SyncHttpRequestClient extends SyncRequestClient {
-  SyncHttpRequestClient(Uri prefix) : super(prefix);
+  final Map<String, String> _headers;
+  SyncHttpRequestClient(Uri prefix, {Map<String, String> headers = const {}})
+      : _headers = headers,
+        super(prefix);
 
   @override
   WebDriverResponse sendRaw(WebDriverRequest request) {
@@ -27,6 +30,7 @@
         break;
     }
 
+    _headers.forEach(httpRequest.headers.add);
     httpRequest.headers.add(HttpHeaders.acceptHeader, 'application/json');
     httpRequest.headers.add(HttpHeaders.cacheControlHeader, 'no-cache');
 
diff --git a/lib/src/sync/logs.dart b/lib/src/sync/logs.dart
index 59962ae..4a51473 100644
--- a/lib/src/sync/logs.dart
+++ b/lib/src/sync/logs.dart
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import 'package:webdriver/src/common/exception.dart';
 import 'package:webdriver/src/common/log.dart';
 import 'package:webdriver/src/common/request_client.dart';
 import 'package:webdriver/src/common/webdriver_handler.dart';
@@ -26,8 +27,8 @@
     try {
       return _client.send(_handler.logs.buildGetLogsRequest(logType),
           _handler.logs.parseGetLogsResponse);
-    } on UnsupportedError {
-      // Produces no entries for W3C/Firefox.
+    } on UnknownCommandException {
+      // Produces no entries for Firefox.
       return <LogEntry>[];
     }
   }
diff --git a/lib/src/sync/mouse.dart b/lib/src/sync/mouse.dart
index 51a2582..fd3db71 100644
--- a/lib/src/sync/mouse.dart
+++ b/lib/src/sync/mouse.dart
@@ -15,6 +15,8 @@
 import 'package:webdriver/src/common/mouse.dart';
 import 'package:webdriver/src/common/request_client.dart';
 import 'package:webdriver/src/common/webdriver_handler.dart';
+import 'package:webdriver/src/handler/json_wire_handler.dart';
+import 'package:webdriver/src/handler/w3c_handler.dart';
 
 import 'web_element.dart';
 
@@ -61,8 +63,12 @@
   /// 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].
+  /// If all three are specified, the behavior will be different
+  /// for W3C and JsonWire. For W3C, it will use [element] center as the
+  /// origin, while for JsonWire, it will use [element] top left corner.
+  /// To get a consistent behavior across browsers, you can try
+  /// [moveToElementCenter] and [moveToElementTopLeft] to specify the origin you
+  /// would like to use.
   ///
   /// All other combinations of parameters are illegal.
   ///
@@ -79,6 +85,45 @@
         _handler.mouse.parseMoveToResponse);
   }
 
+  /// Moves to [element], with an offset of [xOffset] and [yOffset] based on the
+  /// center of [element].
+  void moveToElementCenter(WebElement element, {int xOffset, int yOffset}) {
+    if (_handler is JsonWireWebDriverHandler) {
+      final size = element.size;
+      moveTo(
+          element: element,
+          xOffset: (xOffset ?? 0) + size.width ~/ 2,
+          yOffset: (yOffset ?? 0) + size.height ~/ 2);
+    } else {
+      moveTo(element: element, xOffset: xOffset, yOffset: yOffset);
+    }
+  }
+
+  /// Moves to [element], with an offset of [xOffset] and [yOffset] based on the
+  /// top left corner of [element].
+  void moveToElementTopLeft(WebElement element, {int xOffset, int yOffset}) {
+    if (_handler is W3cWebDriverHandler) {
+      final size = element.size;
+      moveTo(
+          element: element,
+          xOffset: (xOffset ?? 0) - size.width ~/ 2,
+          yOffset: (yOffset ?? 0) - size.height ~/ 2);
+    } else {
+      moveTo(element: element, xOffset: xOffset, yOffset: yOffset);
+    }
+  }
+
+  /// Moves the mouse away to hide its effect, like hover over element.
+  ///
+  /// For W3C, the mouse cannot move out of the screen, the workaround would be
+  /// to move to somewhere on edge where it's not on any element. You can
+  /// configure the location with [w3cXOffset] and [w3cYOffset]. By default,
+  /// it's at (1000, 0).
+  void hide({int w3cXOffset = 1000, int w3cYOffset = 0}) =>
+      _handler is W3cWebDriverHandler
+          ? moveTo(xOffset: w3cXOffset, yOffset: w3cYOffset, absolute: true)
+          : moveTo(xOffset: -10000, yOffset: -10000);
+
   @override
   String toString() => '$_handler.mouse($_client)';
 
diff --git a/lib/src/sync/web_driver.dart b/lib/src/sync/web_driver.dart
index dc6ec5d..6ffe382 100644
--- a/lib/src/sync/web_driver.dart
+++ b/lib/src/sync/web_driver.dart
@@ -128,6 +128,10 @@
       this,
       by);
 
+  /// Search for an element by xpath within the entire current page.
+  /// Throws [NoSuchElementException] if a matching element is not found.
+  WebElement findElementByXpath(String by) => findElement(By.xpath(by));
+
   /// An artist's rendition of the current page's source.
   String get pageSource => _client.send(_handler.core.buildPageSourceRequest(),
       _handler.core.parsePageSourceResponse);
@@ -202,12 +206,24 @@
       _handler.core.buildScreenshotRequest(),
       _handler.core.parseScreenshotResponse);
 
+  /// Take a screenshot of the specified element as PNG and return it as
+  /// base64-encoded string.
+  String captureElementScreenshotAsBase64(WebElement element) => _client.send(
+      _handler.core.buildElementScreenshotRequest(element.id),
+      _handler.core.parseScreenshotResponse);
+
   /// Take a screenshot of the current page as PNG as list of uint8.
   List<int> captureScreenshotAsList() {
     final base64Encoded = captureScreenshotAsBase64();
     return base64.decode(base64Encoded);
   }
 
+  /// Take a screenshot of the specified element as PNG as list of uint8.
+  List<int> captureElementScreenshotAsList(WebElement element) {
+    final base64Encoded = captureElementScreenshotAsBase64(element);
+    return base64.decode(base64Encoded);
+  }
+
   /// 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
diff --git a/lib/src/sync/web_element.dart b/lib/src/sync/web_element.dart
index 6901d91..8bb82a9 100644
--- a/lib/src/sync/web_element.dart
+++ b/lib/src/sync/web_element.dart
@@ -25,7 +25,7 @@
 
 // ignore: uri_does_not_exist
 import 'common_stub.dart'
-    // ignore: uri_does_not_exist
+// ignore: uri_does_not_exist
     if (dart.library.io) 'common_io.dart';
 
 /// WebDriver representation and interactions with an HTML element.
@@ -60,6 +60,39 @@
   WebElement(this.driver, this._client, this._handler, this.id,
       [this.context, this.locator, this.index]);
 
+  WebElement get parent => WebElement(
+      driver,
+      _client,
+      _handler,
+      _client.send(_handler.element.buildPropertyRequest(id, 'parentElement'),
+          _handler.elementFinder.parseFindElementResponse));
+
+  static final _parentCache = <String, String>{};
+
+  /// Gets a chain of parent elements, including the element itself.
+  List<String> get parents {
+    var p = this;
+    final result = <String>[];
+    while (p.id != null) {
+      if (_parentCache.containsKey(p.id)) {
+        break;
+      }
+      result.add(p.id);
+      _parentCache[p.id] = (p = p.parent).id;
+    }
+
+    if (p.id != null) {
+      // Hit cache in the previous loop.
+      var id = p.id;
+      while (id != null) {
+        result.add(id);
+        id = _parentCache[id];
+      }
+    }
+
+    return result;
+  }
+
   /// Click on this element.
   void click() {
     _client.send(_handler.element.buildClickRequest(id),
diff --git a/lib/sync_core.dart b/lib/sync_core.dart
index 336889a..456f155 100644
--- a/lib/sync_core.dart
+++ b/lib/sync_core.dart
@@ -94,7 +94,7 @@
     final handler = getHandler(spec);
 
     session = client.send(handler.session.buildInfoRequest(sessionId),
-        handler.session.parseInfoResponse);
+        (response) => handler.session.parseInfoResponse(response, sessionId));
   }
 
   if (session.spec != WebDriverSpec.JsonWire &&
diff --git a/lib/sync_io.dart b/lib/sync_io.dart
index b3fdd88..9d2b896 100644
--- a/lib/sync_io.dart
+++ b/lib/sync_io.dart
@@ -31,9 +31,13 @@
 core.WebDriver createDriver(
         {Uri uri,
         Map<String, dynamic> desired,
-        core.WebDriverSpec spec = core.WebDriverSpec.Auto}) =>
-    core.createDriver((prefix) => SyncHttpRequestClient(prefix),
-        uri: uri, desired: desired, spec: spec);
+        core.WebDriverSpec spec = core.WebDriverSpec.Auto,
+        Map<String, String> webDriverHeaders = const {}}) =>
+    core.createDriver(
+        (prefix) => SyncHttpRequestClient(prefix, headers: webDriverHeaders),
+        uri: uri,
+        desired: desired,
+        spec: spec);
 
 /// Creates a sync WebDriver from existing session using
 /// [SyncHttpRequestClient].
diff --git a/test/async_command_event_test.dart b/test/async_command_event_test.dart
index 4daa81d..0cf17a1 100644
--- a/test/async_command_event_test.dart
+++ b/test/async_command_event_test.dart
@@ -68,5 +68,5 @@
       expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
       expect(events[1].stackTrace, const TypeMatcher<Chain>());
     });
-  }, testOn: '!js');
+  }, testOn: '!js', timeout: const Timeout(Duration(minutes: 2)));
 }
diff --git a/test/async_cookies_test.dart b/test/async_cookies_test.dart
index a8d87da..2ac93a6 100644
--- a/test/async_cookies_test.dart
+++ b/test/async_cookies_test.dart
@@ -20,6 +20,8 @@
 
 import 'configs/async_io_config.dart' as config;
 
+final _expiryDate = DateTime.now().add(const Duration(days: 180));
+
 void main() {
   group('Cookies', () {
     WebDriver driver;
@@ -44,9 +46,8 @@
     });
 
     test('add complex cookie and get', () async {
-      var date = DateTime.utc(2020);
       await driver.cookies.add(Cookie('mycookie', 'myvalue',
-          path: '/', domain: '.google.com', secure: false, expiry: date));
+          path: '/', domain: '.google.com', secure: false, expiry: _expiryDate));
 
       final cookie = await driver.cookies.getCookie('mycookie');
       expect(cookie.value, 'myvalue');
@@ -55,9 +56,8 @@
 
     test('get all cookies', () async {
       await driver.cookies.add(Cookie('mycookie', 'myvalue'));
-      var date = DateTime.utc(2020);
       await driver.cookies.add(Cookie('mycomplexcookie', 'mycomplexvalue',
-          path: '/', domain: '.google.com', secure: false, expiry: date));
+          path: '/', domain: '.google.com', secure: false, expiry: _expiryDate));
 
       bool found = false;
       await for (var cookie in driver.cookies.all) {
@@ -102,9 +102,8 @@
       // So instead, we plant two cookies and test that they are actually
       // removed by [deleteAll].
       await driver.cookies.add(Cookie('mycookie', 'myvalue'));
-      var date = DateTime.utc(2020);
       await driver.cookies.add(Cookie('mycomplexcookie', 'mycomplexvalue',
-          path: '/', domain: '.google.com', secure: false, expiry: date));
+          path: '/', domain: '.google.com', secure: false, expiry: _expiryDate));
 
       await driver.cookies.deleteAll();
 
@@ -118,5 +117,5 @@
 
       expect(found, isFalse);
     });
-  });
+  }, timeout: const Timeout(Duration(minutes: 2)));
 }
diff --git a/test/async_logs_test.dart b/test/async_logs_test.dart
index bf38610..c885891 100644
--- a/test/async_logs_test.dart
+++ b/test/async_logs_test.dart
@@ -44,7 +44,12 @@
 
     test('get logs', () async {
       List<LogEntry> logs = await driver.logs.get(LogType.performance).toList();
-      expect(logs.length, greaterThan(0));
+      if (driver.capabilities['browserName'] == 'firefox') {
+        expect(logs, isEmpty);
+        return;
+      }
+
+      expect(logs, isNotEmpty);
       logs.forEach((entry) {
         expect(entry.level, equals(LogLevel.info));
       });
diff --git a/test/async_mouse_test.dart b/test/async_mouse_test.dart
index a5f4e41..0706b32 100644
--- a/test/async_mouse_test.dart
+++ b/test/async_mouse_test.dart
@@ -28,6 +28,20 @@
     WebElement button;
     HttpServer server;
 
+    Future<bool> hasAlert() async {
+      try {
+        await driver.switchTo.alert.dismiss();
+        return true;
+      } on NoSuchAlertException {
+        return false;
+      }
+    }
+
+    Future<bool> mouseOnButton() async {
+      await driver.mouse.click();
+      return await hasAlert();
+    }
+
     setUp(() async {
       driver = await config.createTestDriver();
       server = await config.createTestServerAndGoToTestPage(driver);
@@ -41,24 +55,58 @@
 
     test('moveTo element/click', () async {
       await driver.mouse.moveTo(element: button);
-      await driver.mouse.click();
-      var alert = await driver.switchTo.alert;
-      await alert.dismiss();
+      expect(await mouseOnButton(), true);
     });
 
     test('moveTo coordinates/click', () async {
       var pos = await button.location;
       await driver.mouse.moveTo(xOffset: pos.x + 5, yOffset: pos.y + 5);
-      await driver.mouse.click();
-      var alert = await driver.switchTo.alert;
-      await alert.dismiss();
+      expect(await mouseOnButton(), true);
     });
 
     test('moveTo element coordinates/click', () async {
-      await driver.mouse.moveTo(element: button, xOffset: 5, yOffset: 5);
-      await driver.mouse.click();
-      var alert = await driver.switchTo.alert;
-      await alert.dismiss();
+      await driver.mouse.moveTo(element: button, xOffset: 15, yOffset: 15);
+      // W3C uses center and JsonWire uses top left corner.
+      expect(await mouseOnButton(), driver.spec == WebDriverSpec.JsonWire);
+    });
+
+    test('moveTo element coordinates outside of element/click', () async {
+      await driver.mouse.moveTo(element: button, xOffset: -5, yOffset: -5);
+      // W3C uses center and JsonWire uses top left corner.
+      expect(await mouseOnButton(), driver.spec == WebDriverSpec.W3c);
+    });
+
+    test('moveToElementCenter moves to correct positions', () async {
+      await driver.mouse.moveToElementCenter(button, xOffset: -5, yOffset: -5);
+      expect(await mouseOnButton(), true);
+      await driver.mouse.moveToElementCenter(button, xOffset: 15, yOffset: 15);
+      expect(await mouseOnButton(), false);
+    });
+
+    test('moveToElementTopLeft moves to correct positions', () async {
+      await driver.mouse.moveToElementTopLeft(button, xOffset: -5, yOffset: -5);
+      expect(await mouseOnButton(), false);
+      await driver.mouse.moveToElementTopLeft(button, xOffset: 15, yOffset: 15);
+      expect(await mouseOnButton(), true);
+    });
+
+    test('hide moves away from the current location', () async {
+      await driver.mouse.moveTo(element: button);
+      expect(await mouseOnButton(), true);
+      await driver.mouse.hide();
+      expect(await mouseOnButton(), false);
+    });
+
+    test('hide moves to given location in w3c.', () async {
+      if (driver.spec == WebDriverSpec.W3c) {
+        var pos = await button.location;
+        await driver.mouse.moveTo(element: button);
+        expect(await mouseOnButton(), true);
+        await driver.mouse.moveTo(xOffset: 0, yOffset: 0, absolute: true);
+        expect(await mouseOnButton(), false);
+        await driver.mouse.hide(w3cXOffset: pos.x + 5, w3cYOffset: pos.y + 5);
+        expect(await mouseOnButton(), true);
+      }
     });
 
     // TODO(DrMarcII): Better up/down tests
@@ -66,16 +114,14 @@
       await driver.mouse.moveTo(element: button);
       await driver.mouse.down();
       await driver.mouse.up();
-      var alert = await driver.switchTo.alert;
-      await alert.dismiss();
+      expect(await hasAlert(), true);
     });
 
     // TODO(DrMarcII): Better double click test
     test('doubleClick', () async {
       await driver.mouse.moveTo(element: button);
       await driver.mouse.doubleClick();
-      var alert = await driver.switchTo.alert;
-      await alert.dismiss();
+      expect(await hasAlert(), true);
     });
   }, timeout: const Timeout(Duration(minutes: 2)));
 }
diff --git a/test/async_web_driver_test.dart b/test/async_web_driver_test.dart
index 3c26d9e..6eab0fb 100644
--- a/test/async_web_driver_test.dart
+++ b/test/async_web_driver_test.dart
@@ -172,12 +172,26 @@
         expect(screenshot, everyElement(const TypeMatcher<int>()));
       });
 
+      test('captureElementScreenshotAsList', () async {
+        var element = await driver.findElement(const By.tagName('tr'));
+        var screenshot = await driver.captureElementScreenshotAsList(element);
+        expect(screenshot, hasLength(isPositive));
+        expect(screenshot, everyElement(const TypeMatcher<int>()));
+      });
+
       test('captureScreenshotAsBase64', () async {
         var screenshot = await driver.captureScreenshotAsBase64();
         expect(screenshot, hasLength(isPositive));
         expect(screenshot, const TypeMatcher<String>());
       });
 
+      test('captureElementScreenshotAsBase64', () async {
+        var element = await driver.findElement(const By.tagName('tr'));
+        var screenshot = await driver.captureElementScreenshotAsBase64(element);
+        expect(screenshot, hasLength(isPositive));
+        expect(screenshot, const TypeMatcher<String>());
+      });
+
       test('future based event listeners work with script timeouts', () async {
         driver.addEventListener((WebDriverCommandEvent e) async {
           return await Future.delayed(
diff --git a/test/async_web_element_test.dart b/test/async_web_element_test.dart
index 4729a45..c378a1d 100644
--- a/test/async_web_element_test.dart
+++ b/test/async_web_element_test.dart
@@ -33,6 +33,7 @@
     WebElement checkbox;
     WebElement disabled;
     WebElement invisible;
+    WebElement inner;
     HttpServer server;
 
     setUp(() async {
@@ -49,7 +50,8 @@
           .findElement(const By.cssSelector('input[type=checkbox]'));
       disabled = await driver
           .findElement(const By.cssSelector('input[type=password]'));
-      invisible = await driver.findElement(const By.tagName('div'));
+      invisible = await driver.findElement(const By.id('invisible-div'));
+      inner = await driver.findElement(const By.id('inner-div'));
     });
 
     tearDown(() async {
@@ -98,6 +100,7 @@
       expect(await checkbox.displayed, isTrue);
       expect(await disabled.displayed, isTrue);
       expect(await invisible.displayed, isFalse);
+      expect(await inner.displayed, isFalse);
     });
 
     test('location -- table', () async {
@@ -124,7 +127,6 @@
     test('size -- invisible', () async {
       var size = await invisible.size;
       expect(size, config.isRectangle);
-      // TODO(DrMarcII): I thought these should be 0
       expect(size.width, isNonNegative);
       expect(size.height, isNonNegative);
     });
diff --git a/test/configs/async_io_config.dart b/test/configs/async_io_config.dart
index 5eabdfd..9d0ed83 100644
--- a/test/configs/async_io_config.dart
+++ b/test/configs/async_io_config.dart
@@ -63,7 +63,7 @@
     }
   });
 
-  await driver.get('http://localhost:${server.port}/test_page.html');
+  await driver.get('http://$testHostname:${server.port}/test_page.html');
 
   return server;
 }
diff --git a/test/configs/common_config.dart b/test/configs/common_config.dart
index da03925..0933320 100644
--- a/test/configs/common_config.dart
+++ b/test/configs/common_config.dart
@@ -29,6 +29,8 @@
 Future<HttpServer> createLocalServer() =>
     HttpServer.bind(InternetAddress.anyIPv4, 0);
 
+String get testHostname => '127.0.0.1';
+
 String get testHomePath => path.absolute('test');
 
 Uri getWebDriverUri(WebDriverSpec spec) {
diff --git a/test/configs/sync_io_config.dart b/test/configs/sync_io_config.dart
index 8ff3ac9..0795003 100644
--- a/test/configs/sync_io_config.dart
+++ b/test/configs/sync_io_config.dart
@@ -61,7 +61,7 @@
   });
 
   await driver.asyncDriver
-      .get('http://localhost:${server.port}/test_page.html');
+      .get('http://$testHostname:${server.port}/test_page.html');
 
   return server;
 }
diff --git a/test/sync/command_event.dart b/test/sync/command_event.dart
index a92821c..ea6f1cf 100644
--- a/test/sync/command_event.dart
+++ b/test/sync/command_event.dart
@@ -15,6 +15,8 @@
 @TestOn('vm')
 library webdriver.command_event_test;
 
+import 'dart:io';
+
 import 'package:stack_trace/stack_trace.dart';
 import 'package:test/test.dart';
 import 'package:webdriver/sync_core.dart';
@@ -24,19 +26,23 @@
 void runTests({WebDriverSpec spec = WebDriverSpec.Auto}) {
   group('CommandEvent', () {
     WebDriver driver;
+    HttpServer server;
 
     var events = <WebDriverCommandEvent>[];
 
-    setUp(() {
+    setUp(() async {
       driver = config.createTestDriver(spec: spec);
       driver.addEventListener(events.add);
-      driver.get(config.testPagePath);
+
+      server = await config.createTestServerAndGoToTestPage(driver);
     });
 
-    tearDown(() {
+    tearDown(() async {
       driver.quit();
       events.clear();
       driver = null;
+
+      await server?.close(force: true);
     });
 
     test('handles exceptions', () {
@@ -44,22 +50,26 @@
         driver.switchTo.alert.text;
         fail('Expected exception on no alert');
       } catch (NoSuchAlertException) {}
-      expect(events[1].method, 'GET');
-      expect(events[1].endPoint, contains('alert'));
-      expect(events[1].exception, const isInstanceOf<WebDriverException>());
-      expect(events[1].result, isNull);
-      expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
-      expect(events[1].stackTrace, const isInstanceOf<Chain>());
+      // TODO(b/140553567): There should be two events.
+      expect(events, hasLength(1));
+      expect(events[0].method, 'GET');
+      expect(events[0].endPoint, contains('alert'));
+      expect(events[0].exception, const TypeMatcher<WebDriverException>());
+      expect(events[0].result, isNull);
+      expect(events[0].startTime.isBefore(events[0].endTime), isTrue);
+      expect(events[0].stackTrace, const TypeMatcher<Chain>());
     });
 
     test('handles normal operation', () {
       driver.findElements(const By.cssSelector('nosuchelement')).toList();
-      expect(events[1].method, 'POST');
-      expect(events[1].endPoint, contains('elements'));
-      expect(events[1].exception, isNull);
-      expect(events[1].result, isNotNull);
-      expect(events[1].startTime.isBefore(events[1].endTime), isTrue);
-      expect(events[1].stackTrace, const TypeMatcher<Chain>());
+      // TODO(b/140553567): There should be two events.
+      expect(events, hasLength(1));
+      expect(events[0].method, 'POST');
+      expect(events[0].endPoint, contains('elements'));
+      expect(events[0].exception, isNull);
+      expect(events[0].result, isNotNull);
+      expect(events[0].startTime.isBefore(events[0].endTime), isTrue);
+      expect(events[0].stackTrace, const TypeMatcher<Chain>());
     });
   }, timeout: const Timeout(Duration(minutes: 2)));
 }
diff --git a/test/sync/cookies.dart b/test/sync/cookies.dart
index 1bd1a67..c885342 100644
--- a/test/sync/cookies.dart
+++ b/test/sync/cookies.dart
@@ -20,6 +20,8 @@
 
 import '../configs/sync_io_config.dart' as config;
 
+final _expiryDate = DateTime.now().add(const Duration(days: 180));
+
 void runTests({WebDriverSpec spec = WebDriverSpec.Auto}) {
   group('Cookies', () {
     WebDriver driver;
@@ -44,9 +46,11 @@
     });
 
     test('add complex cookie and get', () {
-      var date = DateTime.utc(2020);
       driver.cookies.add(Cookie('mycookie', 'myvalue',
-          path: '/', domain: '.google.com', secure: false, expiry: date));
+          path: '/',
+          domain: '.google.com',
+          secure: false,
+          expiry: _expiryDate));
 
       final cookie = driver.cookies.getCookie('mycookie');
       expect(cookie.value, 'myvalue');
@@ -55,9 +59,11 @@
 
     test('get all cookies', () {
       driver.cookies.add(Cookie('mycookie', 'myvalue'));
-      var date = DateTime.utc(2020);
       driver.cookies.add(Cookie('mycomplexcookie', 'mycomplexvalue',
-          path: '/', domain: '.google.com', secure: false, expiry: date));
+          path: '/',
+          domain: '.google.com',
+          secure: false,
+          expiry: _expiryDate));
 
       bool found = false;
       for (var cookie in driver.cookies.all) {
@@ -101,9 +107,11 @@
       // So instead, we plant two cookies and test that they are actually
       // removed by [deleteAll].
       driver.cookies.add(Cookie('mycookie', 'myvalue'));
-      var date = DateTime.utc(2020);
       driver.cookies.add(Cookie('mycomplexcookie', 'mycomplexvalue',
-          path: '/', domain: '.google.com', secure: false, expiry: date));
+          path: '/',
+          domain: '.google.com',
+          secure: false,
+          expiry: _expiryDate));
 
       driver.cookies.deleteAll();
 
diff --git a/test/sync/logs.dart b/test/sync/logs.dart
index 4689d52..fd30fb9 100644
--- a/test/sync/logs.dart
+++ b/test/sync/logs.dart
@@ -44,7 +44,12 @@
 
     test('get logs', () {
       List<LogEntry> logs = driver.logs.get(LogType.performance).toList();
-      expect(logs.length, greaterThan(0));
+      if (driver.capabilities['browserName'] == 'firefox') {
+        expect(logs, isEmpty);
+        return;
+      }
+
+      expect(logs, isNotEmpty);
       logs.forEach((entry) {
         expect(entry.level, equals(LogLevel.info));
       });
diff --git a/test/sync/mouse.dart b/test/sync/mouse.dart
index 4f3b3f8..fe95015 100644
--- a/test/sync/mouse.dart
+++ b/test/sync/mouse.dart
@@ -28,6 +28,20 @@
     WebElement button;
     HttpServer server;
 
+    bool hasAlert() {
+      try {
+        driver.switchTo.alert.dismiss();
+        return true;
+      } on NoSuchAlertException {
+        return false;
+      }
+    }
+
+    bool mouseOnButton() {
+      driver.mouse.click();
+      return hasAlert();
+    }
+
     setUp(() async {
       driver = config.createTestDriver(spec: spec);
       server = await config.createTestServerAndGoToTestPage(driver);
@@ -41,30 +55,23 @@
 
     test('moveTo element/click', () {
       driver.mouse.moveTo(element: button);
-      driver.mouse.click();
-      var alert = driver.switchTo.alert;
-      alert.dismiss();
+      expect(mouseOnButton(), true);
     });
 
     test('moveTo coordinates/click', () {
       var pos = button.location;
       driver.mouse.moveTo(xOffset: pos.x + 5, yOffset: pos.y + 5);
-      driver.mouse.click();
-      var alert = driver.switchTo.alert;
-      alert.dismiss();
+      expect(mouseOnButton(), true);
     });
 
     test('moveTo absolute coordinates/click', () {
       if (driver.spec == WebDriverSpec.W3c) {
         var pos = button.location;
         driver.mouse.moveTo(xOffset: pos.x + 200, yOffset: pos.y + 200);
-        driver.mouse.click();
-        // Should have no alert
+        expect(mouseOnButton(), false);
         driver.mouse
             .moveTo(xOffset: pos.x + 5, yOffset: pos.y + 5, absolute: true);
-        driver.mouse.click();
-        var alert = driver.switchTo.alert;
-        alert.dismiss();
+        expect(mouseOnButton(), true);
       }
     });
 
@@ -80,10 +87,48 @@
     });
 
     test('moveTo element coordinates/click', () {
-      driver.mouse.moveTo(element: button, xOffset: 5, yOffset: 5);
-      driver.mouse.click();
-      var alert = driver.switchTo.alert;
-      alert.dismiss();
+      driver.mouse.moveTo(element: button, xOffset: 15, yOffset: 15);
+      // W3C uses center and JsonWire uses top left corner.
+      expect(mouseOnButton(), driver.spec == WebDriverSpec.JsonWire);
+    });
+
+    test('moveTo element coordinates outside of element/click', () {
+      driver.mouse.moveTo(element: button, xOffset: -5, yOffset: -5);
+      // W3C uses center and JsonWire uses top left corner.
+      expect(mouseOnButton(), driver.spec == WebDriverSpec.W3c);
+    });
+
+    test('moveToElementCenter moves to correct positions', () {
+      driver.mouse.moveToElementCenter(button, xOffset: -5, yOffset: -5);
+      expect(mouseOnButton(), true);
+      driver.mouse.moveToElementCenter(button, xOffset: 15, yOffset: 15);
+      expect(mouseOnButton(), false);
+    });
+
+    test('moveToElementTopLeft moves to correct positions', () {
+      driver.mouse.moveToElementTopLeft(button, xOffset: -5, yOffset: -5);
+      expect(mouseOnButton(), false);
+      driver.mouse.moveToElementTopLeft(button, xOffset: 15, yOffset: 15);
+      expect(mouseOnButton(), true);
+    });
+
+    test('hide moves away from the current location', () {
+      driver.mouse.moveTo(element: button);
+      expect(mouseOnButton(), true);
+      driver.mouse.hide();
+      expect(mouseOnButton(), false);
+    });
+
+    test('hide moves to given location in w3c.', () {
+      if (driver.spec == WebDriverSpec.W3c) {
+        var pos = button.location;
+        driver.mouse.moveTo(element: button);
+        expect(mouseOnButton(), true);
+        driver.mouse.moveTo(xOffset: 0, yOffset: 0, absolute: true);
+        expect(mouseOnButton(), false);
+        driver.mouse.hide(w3cXOffset: pos.x + 5, w3cYOffset: pos.y + 5);
+        expect(mouseOnButton(), true);
+      }
     });
 
     // TODO(DrMarcII): Better up/down tests
diff --git a/test/sync/navigation.dart b/test/sync/navigation.dart
index 4f16aa9..07c132c 100644
--- a/test/sync/navigation.dart
+++ b/test/sync/navigation.dart
@@ -15,6 +15,8 @@
 @TestOn('vm')
 library webdriver.navigation_test;
 
+import 'dart:io';
+
 import 'package:test/test.dart';
 import 'package:webdriver/sync_core.dart';
 
@@ -23,22 +25,25 @@
 void runTests({WebDriverSpec spec = WebDriverSpec.Auto}) {
   group('Navigation', () {
     WebDriver driver;
+    HttpServer server;
 
-    setUp(() {
+    setUp(() async {
       driver = config.createTestDriver(spec: spec);
-      driver.get(config.testPagePath);
+      server = await config.createTestServerAndGoToTestPage(driver);
     });
 
-    tearDown(() {
+    tearDown(() async {
       if (driver != null) {
         driver.quit();
       }
       driver = null;
+
+      await server?.close(force: true);
     });
 
-    test('refresh', () {
+    test('refresh', () async {
       var element = driver.findElement(const By.tagName('button'));
-      driver.refresh();
+      await driver.asyncDriver.refresh();
       try {
         element.name;
       } on Exception {
diff --git a/test/sync/web_driver.dart b/test/sync/web_driver.dart
index 0e0010b..fab23d7 100644
--- a/test/sync/web_driver.dart
+++ b/test/sync/web_driver.dart
@@ -172,12 +172,26 @@
         expect(screenshot, everyElement(const isInstanceOf<int>()));
       });
 
+      test('captureElementScreenshotAsList', () {
+        var element = driver.findElement(const By.tagName('tr'));
+        var screenshot = driver.captureElementScreenshotAsList(element);
+        expect(screenshot, hasLength(isPositive));
+        expect(screenshot, everyElement(const isInstanceOf<int>()));
+      });
+
       test('captureScreenshotAsBase64', () {
         var screenshot = driver.captureScreenshotAsBase64();
         expect(screenshot, hasLength(isPositive));
         expect(screenshot, const isInstanceOf<String>());
       });
 
+      test('captureElementScreenshotAsBase64', () {
+        var element = driver.findElement(const By.tagName('tr'));
+        var screenshot = driver.captureElementScreenshotAsBase64(element);
+        expect(screenshot, hasLength(isPositive));
+        expect(screenshot, const isInstanceOf<String>());
+      });
+
       test('event listeners work with script timeouts', () {
         try {
           driver.timeouts.setScriptTimeout(const Duration(seconds: 1));
diff --git a/test/sync/web_element.dart b/test/sync/web_element.dart
index ae8e99f..ab870bf 100644
--- a/test/sync/web_element.dart
+++ b/test/sync/web_element.dart
@@ -33,6 +33,7 @@
     WebElement checkbox;
     WebElement disabled;
     WebElement invisible;
+    WebElement inner;
     HttpServer server;
 
     setUp(() async {
@@ -47,7 +48,8 @@
           driver.findElement(const By.cssSelector('input[type=checkbox]'));
       disabled =
           driver.findElement(const By.cssSelector('input[type=password]'));
-      invisible = driver.findElement(const By.tagName('div'));
+      invisible = await driver.findElement(const By.id('invisible-div'));
+      inner = await driver.findElement(const By.id('inner-div'));
     });
 
     tearDown(() async {
@@ -96,6 +98,7 @@
       expect(checkbox.displayed, isTrue);
       expect(disabled.displayed, isTrue);
       expect(invisible.displayed, isFalse);
+      expect(inner.displayed, isFalse);
     });
 
     test('rect -- table', () {
@@ -112,13 +115,8 @@
       expect(rect, config.isRectangle);
       expect(rect.left, 0);
       expect(rect.top, 0);
-      if (driver.spec == WebDriverSpec.W3c) {
-        expect(rect.width, 0);
-        expect(rect.height, 0);
-      } else {
-        expect(rect.width, isNonNegative);
-        expect(rect.height, isNonNegative);
-      }
+      expect(rect.width, isNonNegative);
+      expect(rect.height, isNonNegative);
     });
 
     test('location -- table', () {
@@ -145,13 +143,8 @@
     test('size -- invisible', () {
       var size = invisible.size;
       expect(size, config.isRectangle);
-      if (driver.spec == WebDriverSpec.W3c) {
-        expect(size.width, 0);
-        expect(size.height, 0);
-      } else {
-        expect(size.width, isNonNegative);
-        expect(size.height, isNonNegative);
-      }
+      expect(size.width, isNonNegative);
+      expect(size.height, isNonNegative);
     });
 
     test('name', () {
diff --git a/test/test_page.html b/test/test_page.html
index fbf3123..29cf154 100644
--- a/test/test_page.html
+++ b/test/test_page.html
@@ -49,8 +49,11 @@
     <input type='password' disabled/>
     <input type='submit'/>
 </form>
-<div id='div' style='display: none; background-color: red'>
+<div id='invisible-div' style='display: none; background-color: red'>
     some not displayed text
+    <div id='inner-div'>
+      inner div should not be displayed either.
+    </div>
 </div>
 <a id='settable' href="test_page.html" target="_new">
     Open copy in other window</a>