Add W3C mouse support in Dart webdriver. (#193)
Extracted a common interface and implemented the W3C version via "actions" command.
diff --git a/lib/src/sync/json_wire_spec/mouse.dart b/lib/src/sync/json_wire_spec/mouse.dart
index 4e8d9c5..a5c3bf1 100644
--- a/lib/src/sync/json_wire_spec/mouse.dart
+++ b/lib/src/sync/json_wire_spec/mouse.dart
@@ -15,35 +15,15 @@
import '../common.dart';
import '../web_driver.dart';
import '../web_element.dart';
+import '../mouse.dart';
-class MouseButton {
- /// The primary button is usually the left button or the only button on
- /// single-button devices, used to activate a user interface control or select
- /// text.
- static const MouseButton primary = const MouseButton(0);
-
- /// The auxiliary button is usually the middle button, often combined with a
- /// mouse wheel.
- static const MouseButton auxiliary = const MouseButton(1);
-
- /// The secondary button is usually the right button, often used to display a
- /// context menu.
- static const MouseButton secondary = const MouseButton(2);
-
- final int value;
-
- /// [value] for a mouse button is defined in
- /// https://w3c.github.io/uievents/#widl-MouseEvent-button
- const MouseButton(this.value);
-}
-
-class Mouse {
+class JsonWireMouse extends Mouse {
final WebDriver _driver;
final Resolver _resolver;
- Mouse(this._driver) : _resolver = new Resolver(_driver, '');
+ JsonWireMouse(this._driver) : _resolver = new Resolver(_driver, '');
- /// Click any mouse button (at the coordinates set by the last moveTo).
+ @override
void click([MouseButton button]) {
final json = {};
if (button is MouseButton) {
@@ -52,8 +32,7 @@
_resolver.post('click', json);
}
- /// Click and hold any mouse button (at the coordinates set by the last
- /// moveTo command).
+ @override
void down([MouseButton button]) {
final json = {};
if (button is MouseButton) {
@@ -62,7 +41,7 @@
_resolver.post('buttondown', json);
}
- /// Releases the mouse button previously held (where the mouse is currently at).
+ @override
void up([MouseButton button]) {
final json = {};
if (button is MouseButton) {
@@ -71,22 +50,12 @@
_resolver.post('buttonup', json);
}
- /// Double-clicks at the current mouse coordinates (set by moveTo).
+ @override
void doubleClick() {
_resolver.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.
+ @override
void moveTo({WebElement element, int xOffset, int yOffset}) {
final json = {};
if (element is WebElement) {
@@ -106,5 +75,5 @@
int get hashCode => _driver.hashCode;
@override
- bool operator ==(other) => other is Mouse && other._driver == _driver;
+ bool operator ==(other) => other is JsonWireMouse && other._driver == _driver;
}
diff --git a/lib/src/sync/json_wire_spec/web_driver.dart b/lib/src/sync/json_wire_spec/web_driver.dart
index 2ca3682..9355055 100644
--- a/lib/src/sync/json_wire_spec/web_driver.dart
+++ b/lib/src/sync/json_wire_spec/web_driver.dart
@@ -32,6 +32,7 @@
import '../command_processor.dart';
import '../common.dart';
import '../keyboard.dart';
+import '../mouse.dart';
import '../navigation.dart';
import '../target_locator.dart';
import '../timeouts.dart';
@@ -159,7 +160,7 @@
Keyboard get keyboard => new JsonWireKeyboard(this);
@override
- Mouse get mouse => new Mouse(this);
+ Mouse get mouse => new JsonWireMouse(this);
@override
String captureScreenshotAsBase64() => getRequest('screenshot');
diff --git a/lib/src/sync/mouse.dart b/lib/src/sync/mouse.dart
new file mode 100644
index 0000000..b58075e
--- /dev/null
+++ b/lib/src/sync/mouse.dart
@@ -0,0 +1,64 @@
+// Copyright 2017 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.
+
+import 'web_element.dart';
+
+class MouseButton {
+ /// The primary button is usually the left button or the only button on
+ /// single-button devices, used to activate a user interface control or select
+ /// text.
+ static const MouseButton primary = const MouseButton(0);
+
+ /// The auxiliary button is usually the middle button, often combined with a
+ /// mouse wheel.
+ static const MouseButton auxiliary = const MouseButton(1);
+
+ /// The secondary button is usually the right button, often used to display a
+ /// context menu.
+ static const MouseButton secondary = const MouseButton(2);
+
+ final int value;
+
+ /// [value] for a mouse button is defined in
+ /// https://w3c.github.io/uievents/#widl-MouseEvent-button
+ const MouseButton(this.value);
+}
+
+abstract class Mouse {
+ /// Click any mouse button (at the coordinates set by the last moveTo).
+ void click([MouseButton button]);
+
+ /// Click and hold any mouse button (at the coordinates set by the last
+ /// moveTo command).
+ void down([MouseButton button]);
+
+ /// Releases the mouse button previously held (where the mouse is currently at).
+ void up([MouseButton button]);
+
+ /// Double-clicks at the current mouse coordinates (set by moveTo).
+ void 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.
+ void moveTo({WebElement element, int xOffset, int yOffset});
+}
diff --git a/lib/src/sync/w3c_spec/mouse.dart b/lib/src/sync/w3c_spec/mouse.dart
new file mode 100644
index 0000000..796d4f7
--- /dev/null
+++ b/lib/src/sync/w3c_spec/mouse.dart
@@ -0,0 +1,120 @@
+// Copyright 2017 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.
+
+import '../common.dart';
+import '../web_driver.dart';
+import '../web_element.dart';
+import '../mouse.dart';
+
+class W3cMouse extends Mouse {
+ final WebDriver _driver;
+ final Resolver _resolver;
+
+ W3cMouse(this._driver) : _resolver = new Resolver(_driver, '');
+
+ @override
+ void click([MouseButton button = MouseButton.primary]) {
+ _resolver.post('actions', {
+ 'actions': [
+ {
+ 'type': 'pointer',
+ 'id': 'mouses',
+ 'actions': [
+ {'type': 'pointerDown', 'button': button.value},
+ {'type': 'pointerUp', 'button': button.value}
+ ]
+ }
+ ]
+ });
+ }
+
+ @override
+ void down([MouseButton button = MouseButton.primary]) {
+ _resolver.post('actions', {
+ 'actions': [
+ {
+ 'type': 'pointer',
+ 'id': 'mouses',
+ 'actions': [
+ {'type': 'pointerDown', 'button': button.value}
+ ]
+ }
+ ]
+ });
+ }
+
+ @override
+ void up([MouseButton button = MouseButton.primary]) {
+ _resolver.post('actions', {
+ 'actions': [
+ {
+ 'type': 'pointer',
+ 'id': 'mouses',
+ 'actions': [
+ {'type': 'pointerUp', 'button': button.value}
+ ]
+ }
+ ]
+ });
+ }
+
+ @override
+ void doubleClick() {
+ _resolver.post('actions', {
+ 'actions': [
+ {
+ 'type': 'pointer',
+ 'id': 'mouses',
+ 'actions': [
+ {'type': 'pointerDown', 'button': MouseButton.primary.value},
+ {'type': 'pointerUp', 'button': MouseButton.primary.value},
+ {'type': 'pointerDown', 'button': MouseButton.primary.value},
+ {'type': 'pointerUp', 'button': MouseButton.primary.value}
+ ]
+ }
+ ]
+ });
+ }
+
+ @override
+ void moveTo({WebElement element, int xOffset, int yOffset}) {
+ _resolver.post('actions', {
+ 'actions': [
+ {
+ 'type': 'pointer',
+ 'id': 'mouses',
+ 'actions': [
+ {
+ 'type': 'pointerMove',
+ 'origin': element is WebElement
+ ? {w3cElementStr: element.id}
+ : 'pointer',
+ 'x': xOffset ?? 0,
+ 'y': yOffset ?? 0
+ }
+ ]
+ }
+ ]
+ });
+ }
+
+ @override
+ String toString() => '$_driver.mouse';
+
+ @override
+ int get hashCode => _driver.hashCode;
+
+ @override
+ bool operator ==(other) => other is W3cMouse && other._driver == _driver;
+}
diff --git a/lib/src/sync/w3c_spec/web_driver.dart b/lib/src/sync/w3c_spec/web_driver.dart
index 15f08ad..520df30 100644
--- a/lib/src/sync/w3c_spec/web_driver.dart
+++ b/lib/src/sync/w3c_spec/web_driver.dart
@@ -17,6 +17,7 @@
import 'element_finder.dart';
import 'keyboard.dart';
+import 'mouse.dart';
import 'navigation.dart';
import 'target_locator.dart';
import 'timeouts.dart';
@@ -27,9 +28,9 @@
import '../common_spec/cookies.dart';
import '../keyboard.dart';
+import '../mouse.dart';
// We don't implement this, but we need the types to define the API.
import '../json_wire_spec/logs.dart';
-import '../json_wire_spec/mouse.dart';
import '../command_event.dart';
import '../command_processor.dart';
@@ -144,7 +145,7 @@
Keyboard get keyboard => new W3cKeyboard(this);
@override
- Mouse get mouse => throw 'Unsupported in W3C spec, use Actions instead.';
+ Mouse get mouse => new W3cMouse(this);
@override
String captureScreenshotAsBase64() => getRequest('screenshot');
diff --git a/lib/src/sync/w3c_spec/web_element.dart b/lib/src/sync/w3c_spec/web_element.dart
index 4903aae..bf3e453 100644
--- a/lib/src/sync/w3c_spec/web_element.dart
+++ b/lib/src/sync/w3c_spec/web_element.dart
@@ -83,12 +83,10 @@
bool get displayed => this.cssProperties['display'] != 'none';
@override
- // TODO(staats): better exception.
- Point get location => throw 'Unsupported by W3C spec, use "rect" instead.';
+ Point get location => rect.topLeft;
@override
- Rectangle<int> get size =>
- throw 'Unsupported by W3C spec, use "rect" instead.';
+ Rectangle<int> get size => rect;
@override
Rectangle<int> get rect {
diff --git a/lib/src/sync/web_driver.dart b/lib/src/sync/web_driver.dart
index d2924e8..39cc095 100644
--- a/lib/src/sync/web_driver.dart
+++ b/lib/src/sync/web_driver.dart
@@ -15,6 +15,7 @@
import 'command_event.dart';
import 'common.dart';
import 'keyboard.dart';
+import 'mouse.dart';
import 'navigation.dart';
import 'target_locator.dart';
import 'timeouts.dart';
@@ -25,7 +26,6 @@
import 'common_spec/cookies.dart';
-import 'json_wire_spec/mouse.dart';
import 'json_wire_spec/logs.dart';
typedef void WebDriverListener(WebDriverCommandEvent event);
diff --git a/lib/src/sync/web_element.dart b/lib/src/sync/web_element.dart
index e36563d..4217326 100644
--- a/lib/src/sync/web_element.dart
+++ b/lib/src/sync/web_element.dart
@@ -65,11 +65,9 @@
///
/// This is assumed to be the upper left corner of the element, but its
/// implementation is not well defined in the JSON spec.
- @Deprecated('JSON wire legacy support, emulated for newer browsers')
Point get location;
/// The size of this element.
- @Deprecated('JSON wire legacy support, emulated for newer browsers')
Rectangle<int> get size;
/// The bounds of this element.
diff --git a/lib/sync_core.dart b/lib/sync_core.dart
index 03a396d..268aeae 100644
--- a/lib/sync_core.dart
+++ b/lib/sync_core.dart
@@ -33,11 +33,11 @@
export 'package:webdriver/src/sync/common.dart';
export 'package:webdriver/src/sync/common_spec/cookies.dart';
export 'package:webdriver/src/sync/keyboard.dart';
+export 'package:webdriver/src/sync/mouse.dart';
export 'package:webdriver/src/sync/navigation.dart';
export 'package:webdriver/src/sync/exception.dart';
export 'package:webdriver/src/sync/json_wire_spec/exception.dart';
export 'package:webdriver/src/sync/json_wire_spec/logs.dart';
-export 'package:webdriver/src/sync/json_wire_spec/mouse.dart';
export 'package:webdriver/src/sync/timeouts.dart';
export 'package:webdriver/src/sync/target_locator.dart';
export 'package:webdriver/src/sync/web_driver.dart';
diff --git a/test/firefox_w3c_mouse_test.dart b/test/firefox_w3c_mouse_test.dart
new file mode 100644
index 0000000..9af411a
--- /dev/null
+++ b/test/firefox_w3c_mouse_test.dart
@@ -0,0 +1,20 @@
+// Copyright 2017 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.
+
+import 'sync/mouse.dart';
+import 'sync/sync_io_config.dart' as config;
+
+void main() {
+ runTests(config.createFirefoxTestDriver);
+}
diff --git a/test/sync/w3c_web_element.dart b/test/sync/w3c_web_element.dart
index a4af0f5..c9a6be0 100644
--- a/test/sync/w3c_web_element.dart
+++ b/test/sync/w3c_web_element.dart
@@ -95,23 +95,50 @@
});
test('rect -- table', () {
- var location = table.rect;
- expect(location, config.isRectangle);
- expect(location.left, isNonNegative);
- expect(location.top, isNonNegative);
- expect(location.width, isNonNegative);
- expect(location.height, isNonNegative);
+ var rect = table.rect;
+ expect(rect, config.isRectangle);
+ expect(rect.left, isNonNegative);
+ expect(rect.top, isNonNegative);
+ expect(rect.width, isNonNegative);
+ expect(rect.height, isNonNegative);
+ });
+
+ test('rect -- invisible', () {
+ var rect = invisible.rect;
+ expect(rect, config.isRectangle);
+ expect(rect.left, 0);
+ expect(rect.top, 0);
+ expect(rect.width, 0);
+ expect(rect.height, 0);
+ });
+
+ test('location -- table', () {
+ var location = table.location;
+ expect(location, config.isPoint);
+ expect(location.x, isNonNegative);
+ expect(location.y, isNonNegative);
});
test('location -- invisible', () {
- var location = invisible.rect;
- expect(location, config.isRectangle);
- expect(location.left, 0);
- expect(location.top, 0);
- expect(location.width, 0);
- expect(location.height, 0);
+ var location = invisible.location;
+ expect(location, config.isPoint);
+ expect(location.x, 0);
+ expect(location.y, 0);
});
+ test('size -- table', () {
+ var size = table.size;
+ expect(size, config.isRectangle);
+ expect(size.width, isNonNegative);
+ expect(size.height, isNonNegative);
+ });
+
+ test('size -- invisible', () {
+ var size = invisible.size;
+ expect(size, config.isRectangle);
+ expect(size.width, 0);
+ expect(size.height, 0);
+ });
test('name', () {
expect(table.name, 'table');
expect(button.name, 'button');