Add moveToElementCenter and moveToElementTopLeft to declare the origin for offset explicitly.

Explicitly include JsonWire Chrome in tests.

PiperOrigin-RevId: 254157910
diff --git a/lib/src/async/mouse.dart b/lib/src/async/mouse.dart
index 4529e49..9c57146 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,36 @@
               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);
+    }
+  }
+
   @override
   String toString() => '$_handler.mouse($_client)';
 
diff --git a/lib/src/sync/mouse.dart b/lib/src/sync/mouse.dart
index 51a2582..d858d43 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,34 @@
         _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);
+    }
+  }
+
   @override
   String toString() => '$_handler.mouse($_client)';
 
diff --git a/test/async_mouse_test.dart b/test/async_mouse_test.dart
index a5f4e41..1f092b7 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,39 @@
 
     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);
     });
 
     // TODO(DrMarcII): Better up/down tests
@@ -66,16 +95,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/sync/mouse.dart b/test/sync/mouse.dart
index 4f3b3f8..a8a67a8 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,29 @@
     });
 
     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);
     });
 
     // TODO(DrMarcII): Better up/down tests