Add W3C keyboard support in Dart webdriver. (#191)
* Add W3C keyboard support in Dart webdriver.
Extracted a common interface, and implemented it in the W3C version via "actions" command.
Also some minor fixes in W3C webdriver setup.
* Convert all quotes to single.
diff --git a/lib/src/sync/json_wire_spec/keyboard.dart b/lib/src/sync/json_wire_spec/keyboard.dart
index 627bbe6..9240c36 100644
--- a/lib/src/sync/json_wire_spec/keyboard.dart
+++ b/lib/src/sync/json_wire_spec/keyboard.dart
@@ -13,88 +13,31 @@
// limitations under the License.
import '../common.dart';
+import '../keyboard.dart';
import '../web_driver.dart';
-class Keyboard {
- static const String nullChar = '\uE000';
- static const String cancel = '\uE001';
- static const String help = '\uE002';
- static const String backSpace = '\uE003';
- static const String tab = '\uE004';
- static const String clear = '\uE005';
- static const String returnChar = '\uE006';
- static const String enter = '\uE007';
- static const String shift = '\uE008';
- static const String control = '\uE009';
- static const String alt = '\uE00A';
- static const String pause = '\uE00B';
- static const String escape = '\uE00C';
- static const String space = '\uE00D';
- static const String pageUp = '\uE00E';
- static const String pageDown = '\uE00F';
- static const String end = '\uE010';
- static const String home = '\uE011';
- static const String left = '\uE012';
- static const String up = '\uE013';
- static const String right = '\uE014';
- static const String down = '\uE015';
- static const String insert = '\uE016';
- static const String deleteChar = '\uE017';
- static const String semicolon = '\uE018';
- static const String equals = '\uE019';
- static const String numpad0 = '\uE01A';
- static const String numpad1 = '\uE01B';
- static const String numpad2 = '\uE01C';
- static const String numpad3 = '\uE01D';
- static const String numpad4 = '\uE01E';
- static const String numpad5 = '\uE01F';
- static const String numpad6 = '\uE020';
- static const String numpad7 = '\uE021';
- static const String numpad8 = '\uE022';
- static const String numpad9 = '\uE023';
- static const String multiply = '\uE024';
- static const String add = '\uE025';
- static const String separator = '\uE026';
- static const String subtract = '\uE027';
- static const String decimal = '\uE028';
- static const String divide = '\uE029';
- static const String f1 = '\uE031';
- static const String f2 = '\uE032';
- static const String f3 = '\uE033';
- static const String f4 = '\uE034';
- static const String f5 = '\uE035';
- static const String f6 = '\uE036';
- static const String f7 = '\uE037';
- static const String f8 = '\uE038';
- static const String f9 = '\uE039';
- static const String f10 = '\uE03A';
- static const String f11 = '\uE03B';
- static const String f12 = '\uE03C';
- static const String command = '\uE03D';
- static const String meta = command;
-
+class JsonWireKeyboard extends Keyboard {
final WebDriver _driver;
final Resolver _resolver;
- Keyboard(this._driver) : _resolver = new Resolver(_driver, '');
+ JsonWireKeyboard(this._driver) : _resolver = new Resolver(_driver, '');
- /// Simulate pressing many keys at once as a 'chord'.
+ @override
void sendChord(Iterable<String> chordToSend) {
- sendKeys(createChord(chordToSend));
+ sendKeys(_createChord(chordToSend));
}
- /// Creates a string representation of a chord suitable for use in WebDriver.
- String createChord(Iterable<String> chord) {
+ String _createChord(Iterable<String> chord) {
StringBuffer chordString = new StringBuffer();
for (String s in chord) {
chordString.write(s);
}
- chordString.write(nullChar);
+ chordString.write(Keyboard.nullChar);
return chordString.toString();
}
- /// Send [keysToSend] to the active element.
+ @override
void sendKeys(String keysToSend) {
_resolver.post('keys', {
'value': [keysToSend]
@@ -108,5 +51,6 @@
int get hashCode => _driver.hashCode;
@override
- bool operator ==(other) => other is Keyboard && other._driver == _driver;
+ bool operator ==(other) =>
+ other is JsonWireKeyboard && 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 9c4a55a..2ca3682 100644
--- a/lib/src/sync/json_wire_spec/web_driver.dart
+++ b/lib/src/sync/json_wire_spec/web_driver.dart
@@ -31,6 +31,7 @@
import '../command_event.dart';
import '../command_processor.dart';
import '../common.dart';
+import '../keyboard.dart';
import '../navigation.dart';
import '../target_locator.dart';
import '../timeouts.dart';
@@ -155,7 +156,7 @@
Timeouts get timeouts => new JsonWireTimeouts(this);
@override
- Keyboard get keyboard => new Keyboard(this);
+ Keyboard get keyboard => new JsonWireKeyboard(this);
@override
Mouse get mouse => new Mouse(this);
diff --git a/lib/src/sync/keyboard.dart b/lib/src/sync/keyboard.dart
new file mode 100644
index 0000000..3bd3135
--- /dev/null
+++ b/lib/src/sync/keyboard.dart
@@ -0,0 +1,78 @@
+// 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.
+
+abstract class Keyboard {
+ static const String nullChar = '\uE000';
+ static const String cancel = '\uE001';
+ static const String help = '\uE002';
+ static const String backSpace = '\uE003';
+ static const String tab = '\uE004';
+ static const String clear = '\uE005';
+ static const String returnChar = '\uE006';
+ static const String enter = '\uE007';
+ static const String shift = '\uE008';
+ static const String control = '\uE009';
+ static const String alt = '\uE00A';
+ static const String pause = '\uE00B';
+ static const String escape = '\uE00C';
+ static const String space = '\uE00D';
+ static const String pageUp = '\uE00E';
+ static const String pageDown = '\uE00F';
+ static const String end = '\uE010';
+ static const String home = '\uE011';
+ static const String left = '\uE012';
+ static const String up = '\uE013';
+ static const String right = '\uE014';
+ static const String down = '\uE015';
+ static const String insert = '\uE016';
+ static const String deleteChar = '\uE017';
+ static const String semicolon = '\uE018';
+ static const String equals = '\uE019';
+ static const String numpad0 = '\uE01A';
+ static const String numpad1 = '\uE01B';
+ static const String numpad2 = '\uE01C';
+ static const String numpad3 = '\uE01D';
+ static const String numpad4 = '\uE01E';
+ static const String numpad5 = '\uE01F';
+ static const String numpad6 = '\uE020';
+ static const String numpad7 = '\uE021';
+ static const String numpad8 = '\uE022';
+ static const String numpad9 = '\uE023';
+ static const String multiply = '\uE024';
+ static const String add = '\uE025';
+ static const String separator = '\uE026';
+ static const String subtract = '\uE027';
+ static const String decimal = '\uE028';
+ static const String divide = '\uE029';
+ static const String f1 = '\uE031';
+ static const String f2 = '\uE032';
+ static const String f3 = '\uE033';
+ static const String f4 = '\uE034';
+ static const String f5 = '\uE035';
+ static const String f6 = '\uE036';
+ static const String f7 = '\uE037';
+ static const String f8 = '\uE038';
+ static const String f9 = '\uE039';
+ static const String f10 = '\uE03A';
+ static const String f11 = '\uE03B';
+ static const String f12 = '\uE03C';
+ static const String command = '\uE03D';
+ static const String meta = command;
+
+ /// Simulate pressing many keys at once as a 'chord'.
+ void sendChord(Iterable<String> chordToSend);
+
+ /// Send [keysToSend] to the active element.
+ void sendKeys(String keysToSend);
+}
diff --git a/lib/src/sync/w3c_spec/keyboard.dart b/lib/src/sync/w3c_spec/keyboard.dart
new file mode 100644
index 0000000..7a4bd05
--- /dev/null
+++ b/lib/src/sync/w3c_spec/keyboard.dart
@@ -0,0 +1,67 @@
+// 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 '../keyboard.dart';
+import '../web_driver.dart';
+
+class W3cKeyboard extends Keyboard {
+ final WebDriver _driver;
+ final Resolver _resolver;
+
+ W3cKeyboard(this._driver) : _resolver = new Resolver(_driver, '');
+
+ @override
+ void sendChord(Iterable<String> chordToSend) {
+ final keyDownActions = <Map<String, String>>[];
+ final keyUpActions = <Map<String, String>>[];
+ for (String s in chordToSend) {
+ keyDownActions.add({'type': 'keyDown', 'value': s});
+ keyUpActions.add({'type': 'keyUp', 'value': s});
+ }
+ _resolver.post('actions', {
+ 'actions': [
+ {
+ 'type': 'key',
+ 'id': 'keys',
+ 'actions':
+ keyDownActions + keyUpActions.reversed.toList(growable: false)
+ }
+ ]
+ });
+ }
+
+ @override
+ void sendKeys(String keysToSend) {
+ final keyActions = <Map<String, String>>[];
+ for (int i = 0; i < keysToSend.length; ++i) {
+ keyActions.add({'type': 'keyDown', 'value': keysToSend[i]});
+ keyActions.add({'type': 'keyUp', 'value': keysToSend[i]});
+ }
+ _resolver.post('actions', {
+ 'actions': [
+ {'type': 'key', 'id': 'keys', 'actions': keyActions}
+ ]
+ });
+ }
+
+ @override
+ String toString() => '$_driver.keyboard';
+
+ @override
+ int get hashCode => _driver.hashCode;
+
+ @override
+ bool operator ==(other) => other is W3cKeyboard && other._driver == _driver;
+}
diff --git a/lib/src/sync/w3c_spec/web_driver.dart b/lib/src/sync/w3c_spec/web_driver.dart
index acbd3c1..15f08ad 100644
--- a/lib/src/sync/w3c_spec/web_driver.dart
+++ b/lib/src/sync/w3c_spec/web_driver.dart
@@ -16,6 +16,7 @@
import 'package:stack_trace/stack_trace.dart' show Chain;
import 'element_finder.dart';
+import 'keyboard.dart';
import 'navigation.dart';
import 'target_locator.dart';
import 'timeouts.dart';
@@ -25,8 +26,8 @@
import '../../../async_core.dart' as async_core;
import '../common_spec/cookies.dart';
+import '../keyboard.dart';
// We don't implement this, but we need the types to define the API.
-import '../json_wire_spec/keyboard.dart';
import '../json_wire_spec/logs.dart';
import '../json_wire_spec/mouse.dart';
@@ -140,8 +141,7 @@
Logs get logs => throw 'Unsupported in W3C spec.';
@override
- Keyboard get keyboard =>
- throw 'Unsupported in W3C spec, use Actions instead.';
+ Keyboard get keyboard => new W3cKeyboard(this);
@override
Mouse get mouse => throw 'Unsupported in W3C spec, use Actions instead.';
diff --git a/lib/src/sync/web_driver.dart b/lib/src/sync/web_driver.dart
index a77fa76..d2924e8 100644
--- a/lib/src/sync/web_driver.dart
+++ b/lib/src/sync/web_driver.dart
@@ -14,6 +14,7 @@
import 'command_event.dart';
import 'common.dart';
+import 'keyboard.dart';
import 'navigation.dart';
import 'target_locator.dart';
import 'timeouts.dart';
@@ -26,7 +27,6 @@
import 'json_wire_spec/mouse.dart';
import 'json_wire_spec/logs.dart';
-import 'json_wire_spec/keyboard.dart';
typedef void WebDriverListener(WebDriverCommandEvent event);
@@ -98,9 +98,6 @@
Timeouts get timeouts;
- // TODO(staats): add actions support.
-
- @Deprecated('This not supported in the W3C spec. Use actions instead.')
Keyboard get keyboard;
@Deprecated('This not supported in the W3C spec. Use actions instead.')
diff --git a/lib/sync_core.dart b/lib/sync_core.dart
index 1ed14ba..03a396d 100644
--- a/lib/sync_core.dart
+++ b/lib/sync_core.dart
@@ -32,10 +32,10 @@
export 'package:webdriver/src/sync/command_processor.dart';
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/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/keyboard.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';
@@ -53,7 +53,7 @@
WebDriver createDriver(
{Uri uri,
Map<String, dynamic> desired,
- WebDriverSpec spec = WebDriverSpec.JsonWire}) {
+ WebDriverSpec spec = WebDriverSpec.Auto}) {
uri ??= defaultUri;
desired ??= Capabilities.empty;
@@ -71,14 +71,22 @@
final processor =
new SyncHttpCommandProcessor(processor: processW3cResponse);
final response = processor.post(
- uri.resolve('session'), {'desiredCapabilities': desired},
+ uri.resolve('session'),
+ {
+ 'capabilities': {'desiredCapabilities': desired}
+ },
value: true) as Map<String, dynamic>;
return new w3c.W3cWebDriver(processor, uri, response['sessionId'],
new UnmodifiableMapView(response['value'] as Map<String, dynamic>));
case WebDriverSpec.Auto:
final response =
new SyncHttpCommandProcessor(processor: inferSessionResponseSpec)
- .post(uri.resolve('session'), {'desiredCapabilities': desired},
+ .post(
+ uri.resolve('session'),
+ {
+ 'desiredCapabilities': desired,
+ 'capabilities': {'desiredCapabilities': desired}
+ },
value: true) as InferredResponse;
return fromExistingSession(response.sessionId,
uri: uri, spec: response.spec);
diff --git a/lib/sync_io.dart b/lib/sync_io.dart
index 2a35e32..3c91056 100644
--- a/lib/sync_io.dart
+++ b/lib/sync_io.dart
@@ -28,7 +28,7 @@
core.WebDriver createDriver(
{Uri uri,
Map<String, dynamic> desired,
- core.WebDriverSpec spec = core.WebDriverSpec.JsonWire}) =>
+ core.WebDriverSpec spec = core.WebDriverSpec.Auto}) =>
core.createDriver(uri: uri, desired: desired, spec: spec);
/// Creates a WebDriver instance connected to an existing session.
diff --git a/test/firefox_w3c_keyboard_test.dart b/test/firefox_w3c_keyboard_test.dart
new file mode 100644
index 0000000..1c9f357
--- /dev/null
+++ b/test/firefox_w3c_keyboard_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/keyboard.dart';
+import 'sync/sync_io_config.dart' as config;
+
+void main() {
+ runTests(config.createFirefoxTestDriver);
+}
diff --git a/test/sync/keyboard.dart b/test/sync/keyboard.dart
index 405a7e8..2b8fe22 100644
--- a/test/sync/keyboard.dart
+++ b/test/sync/keyboard.dart
@@ -50,28 +50,37 @@
test('sendKeys -- once', () {
driver.keyboard.sendKeys('abcdef');
- expect(textInput.attributes['value'], 'abcdef');
+ expect(valueOf(textInput), 'abcdef');
});
test('sendKeys -- twice', () {
driver.keyboard.sendKeys('abc');
driver.keyboard.sendKeys('def');
- expect(textInput.attributes['value'], 'abcdef');
+ expect(valueOf(textInput), 'abcdef');
});
test('sendKeys -- with tab', () {
driver.keyboard.sendKeys('abc${Keyboard.tab}def');
- expect(textInput.attributes['value'], 'abc');
+ expect(valueOf(textInput), 'abc');
});
// NOTE: does not work on Mac.
test('sendChord -- CTRL+X', () {
driver.keyboard.sendKeys('abcdef');
- expect(textInput.attributes['value'], 'abcdef');
+ expect(valueOf(textInput), 'abcdef');
driver.keyboard.sendChord([ctrlCmdKey, 'a']);
driver.keyboard.sendChord([ctrlCmdKey, 'x']);
driver.keyboard.sendKeys('xxx');
- expect(textInput.attributes['value'], 'xxx');
+ expect(valueOf(textInput), 'xxx');
});
}, timeout: const Timeout(const Duration(minutes: 2)));
}
+
+/// Gets the "value" property of a [WebElement].
+///
+/// The behavior for the "value" property of a text input is different for
+/// different specs (json wire updates it through attribute and W3C updates it
+/// through property).
+String valueOf(WebElement textInput) {
+ return textInput.attributes['value'] ?? textInput.properties['value'];
+}
\ No newline at end of file