summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-13 03:11:21 +0000
committerkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-13 03:11:21 +0000
commit7cc2c91be249eff44e492eea4ffc095abc4e0b59 (patch)
treecc58213c6285a91254729fefdbb26dbebdf9d795
parent8f7c7cd0eb53a8336a2db1d2b90818305e76706b (diff)
downloadchromium_src-7cc2c91be249eff44e492eea4ffc095abc4e0b59.zip
chromium_src-7cc2c91be249eff44e492eea4ffc095abc4e0b59.tar.gz
chromium_src-7cc2c91be249eff44e492eea4ffc095abc4e0b59.tar.bz2
Implement Value command in ChromeDriver.
BUG=none TEST=none Review URL: http://codereview.chromium.org/6482014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@74748 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/chrome_tests.gypi8
-rw-r--r--chrome/test/automation/browser_proxy.cc8
-rw-r--r--chrome/test/webdriver/WEBDRIVER_TESTS4
-rw-r--r--chrome/test/webdriver/automation.cc22
-rw-r--r--chrome/test/webdriver/automation.h25
-rw-r--r--chrome/test/webdriver/commands/command.cc5
-rw-r--r--chrome/test/webdriver/commands/command.h4
-rw-r--r--chrome/test/webdriver/commands/webelement_command.cc85
-rw-r--r--chrome/test/webdriver/commands/webelement_command.h42
-rw-r--r--chrome/test/webdriver/commands/webelement_commands.cc180
-rw-r--r--chrome/test/webdriver/commands/webelement_commands.h81
-rw-r--r--chrome/test/webdriver/server.cc5
-rw-r--r--chrome/test/webdriver/session.cc43
-rw-r--r--chrome/test/webdriver/session.h6
-rw-r--r--chrome/test/webdriver/webdriver_key_converter.cc279
-rw-r--r--chrome/test/webdriver/webdriver_key_converter.h31
-rw-r--r--chrome/test/webdriver/webdriver_key_converter_unittest.cc151
17 files changed, 845 insertions, 134 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index ff26102..b20a8bf 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -736,6 +736,8 @@
'test/webdriver/session_manager.cc',
'test/webdriver/utility_functions.h',
'test/webdriver/utility_functions.cc',
+ 'test/webdriver/webdriver_key_converter.h',
+ 'test/webdriver/webdriver_key_converter.cc',
'test/webdriver/commands/command.h',
'test/webdriver/commands/command.cc',
'test/webdriver/commands/create_session.h',
@@ -761,8 +763,8 @@
'test/webdriver/commands/url_command.cc',
'test/webdriver/commands/webdriver_command.h',
'test/webdriver/commands/webdriver_command.cc',
- 'test/webdriver/commands/webelement_command.h',
- 'test/webdriver/commands/webelement_command.cc',
+ 'test/webdriver/commands/webelement_commands.h',
+ 'test/webdriver/commands/webelement_commands.cc',
],
'conditions': [
['OS=="linux"', {
@@ -837,6 +839,7 @@
'chromedriver_lib',
'../base/base.gyp:test_support_base',
'../testing/gtest.gyp:gtest',
+ '../skia/skia.gyp:skia',
],
'include_dirs': [
'..',
@@ -844,6 +847,7 @@
'sources': [
'../base/test/run_all_unittests.cc',
'test/webdriver/utility_functions_unittest.cc',
+ 'test/webdriver/webdriver_key_converter_unittest.cc',
],
'conditions': [
['OS=="win"', {
diff --git a/chrome/test/automation/browser_proxy.cc b/chrome/test/automation/browser_proxy.cc
index 316815c..1016223 100644
--- a/chrome/test/automation/browser_proxy.cc
+++ b/chrome/test/automation/browser_proxy.cc
@@ -581,9 +581,11 @@ bool BrowserProxy::SendJSONRequest(const std::string& request,
return false;
bool result = false;
- return sender_->Send(new AutomationMsg_SendJSONRequest(handle_,
- request, response,
- &result));
+ if (!sender_->Send(new AutomationMsg_SendJSONRequest(handle_,
+ request,
+ response,
+ &result)))
+ return false;
return result;
}
diff --git a/chrome/test/webdriver/WEBDRIVER_TESTS b/chrome/test/webdriver/WEBDRIVER_TESTS
index a9325c8..bc5a697 100644
--- a/chrome/test/webdriver/WEBDRIVER_TESTS
+++ b/chrome/test/webdriver/WEBDRIVER_TESTS
@@ -79,7 +79,9 @@
],
'win': [
-
+ 'typing_tests',
+ # needs clear command
+ '-typing_tests.TypingTests.testNumberpadAndFunctionKeys',
],
'mac': [
diff --git a/chrome/test/webdriver/automation.cc b/chrome/test/webdriver/automation.cc
index e0afa4d..eda6488 100644
--- a/chrome/test/webdriver/automation.cc
+++ b/chrome/test/webdriver/automation.cc
@@ -5,8 +5,10 @@
#include "chrome/test/webdriver/automation.h"
#include "base/command_line.h"
+#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/utf_string_conversions.h"
+#include "base/values.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/automation/browser_proxy.h"
#include "chrome/test/automation/tab_proxy.h"
@@ -65,6 +67,26 @@ void Automation::ExecuteScript(const std::string& frame_xpath,
*result = WideToUTF8(wide_result);
}
+void Automation::SendWebKeyEvent(const WebKeyEvent& key_event,
+ bool* success) {
+ scoped_ptr<DictionaryValue> dict(new DictionaryValue);
+ dict->SetString("command", "SendKeyEventToActiveTab");
+ dict->SetInteger("type", key_event.type);
+ dict->SetInteger("nativeKeyCode", key_event.key_code);
+ dict->SetInteger("windowsKeyCode", key_event.key_code);
+ dict->SetString("unmodifiedText", key_event.unmodified_text);
+ dict->SetString("text", key_event.modified_text);
+ dict->SetInteger("modifiers", key_event.modifiers);
+ dict->SetBoolean("isSystemKey", false);
+ std::string request;
+ base::JSONWriter::Write(dict.get(), false, &request);
+ std::string reply;
+ *success = browser_->SendJSONRequest(request, &reply);
+ if (!*success) {
+ LOG(ERROR) << "Could not send web key event. Reply: " << reply;
+ }
+}
+
void Automation::NavigateToURL(const std::string& url,
bool* success) {
*success = tab_->NavigateToURL(GURL(url));
diff --git a/chrome/test/webdriver/automation.h b/chrome/test/webdriver/automation.h
index 10ab4a1..ac7d405 100644
--- a/chrome/test/webdriver/automation.h
+++ b/chrome/test/webdriver/automation.h
@@ -10,10 +10,31 @@
#include "base/task.h"
#include "base/ref_counted.h"
#include "base/scoped_temp_dir.h"
+#include "chrome/common/automation_constants.h"
#include "chrome/test/ui/ui_test.h"
+#include "ui/base/keycodes/keyboard_codes.h"
namespace webdriver {
+struct WebKeyEvent {
+ WebKeyEvent(automation::KeyEventTypes type,
+ ui::KeyboardCode key_code,
+ const std::string& unmodified_text,
+ const std::string& modified_text,
+ int modifiers)
+ : type(type),
+ key_code(key_code),
+ unmodified_text(unmodified_text),
+ modified_text(modified_text),
+ modifiers(modifiers) {}
+
+ automation::KeyEventTypes type;
+ ui::KeyboardCode key_code;
+ std::string unmodified_text;
+ std::string modified_text;
+ int modifiers;
+};
+
// Creates and controls the Chrome instance.
// This class should be created and accessed on a single thread.
// TODO(phajdan.jr): Abstract UITestBase classes, see:
@@ -37,6 +58,10 @@ class Automation : private UITestBase {
std::string* result,
bool* success);
+ // Sends a key event to the current browser. Waits until the key has
+ // been processed by the web page.
+ void SendWebKeyEvent(const WebKeyEvent& key_event, bool* success);
+
void NavigateToURL(const std::string& url, bool* success);
void GoForward(bool* success);
void GoBack(bool* success);
diff --git a/chrome/test/webdriver/commands/command.cc b/chrome/test/webdriver/commands/command.cc
index 142c947..68a0cdb 100644
--- a/chrome/test/webdriver/commands/command.cc
+++ b/chrome/test/webdriver/commands/command.cc
@@ -65,5 +65,10 @@ bool Command::GetIntegerParameter(const std::string& key,
return parameters_.get() != NULL && parameters_->GetInteger(key, out);
}
+bool Command::GetListParameter(const std::string& key,
+ ListValue** out) const {
+ return parameters_.get() != NULL && parameters_->GetList(key, out);
+}
+
} // namespace webdriver
diff --git a/chrome/test/webdriver/commands/command.h b/chrome/test/webdriver/commands/command.h
index ccb95b8..6b3f692 100644
--- a/chrome/test/webdriver/commands/command.h
+++ b/chrome/test/webdriver/commands/command.h
@@ -74,6 +74,10 @@ class Command {
// false if there is no such parameter, or if it is not a int.
bool GetIntegerParameter(const std::string& key, int* out) const;
+ // Returns the command parameter with the given |key| as a list. Returns
+ // false if there is no such parameter, or if it is not a list.
+ bool GetListParameter(const std::string& key, ListValue** out) const;
+
private:
const std::vector<std::string> path_segments_;
const scoped_ptr<const DictionaryValue> parameters_;
diff --git a/chrome/test/webdriver/commands/webelement_command.cc b/chrome/test/webdriver/commands/webelement_command.cc
deleted file mode 100644
index 60230e6..0000000
--- a/chrome/test/webdriver/commands/webelement_command.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/test/webdriver/commands/webelement_command.h"
-
-#include "third_party/webdriver/atoms.h"
-#include "chrome/test/webdriver/error_codes.h"
-#include "chrome/test/webdriver/utility_functions.h"
-
-namespace webdriver {
-
-bool WebElementCommand::Init(Response* const response) {
- if (WebDriverCommand::Init(response)) {
- SET_WEBDRIVER_ERROR(response, "Failure on Init for web element command",
- kInternalServerError);
- return false;
- }
-
- // There should be at least 5 segments to match
- // "/session/$session/element/$id"
- if (path_segments_.size() < 5) {
- SET_WEBDRIVER_ERROR(response, "Path segments is less than 5",
- kBadRequest);
- return false;
- }
-
- // We cannot verify the ID is valid until we execute the command and
- // inject the ID into the in-page cache.
- element_id = path_segments_.at(4);
- return true;
-}
-
-bool WebElementCommand::GetElementLocation(bool in_view, int* x, int* y) {
- scoped_ptr<ListValue> args(new ListValue());
- Value* result = NULL;
-
- std::string jscript = build_atom(GET_LOCATION, sizeof GET_LOCATION);
- if (in_view) {
- jscript.append("arguments[0].scrollIntoView();");
- }
- jscript.append("return getLocation(arguments[0]);");
-
- args->Append(GetElementIdAsDictionaryValue(element_id));
-
- ErrorCode error = session_->ExecuteScript(jscript, args.get(), &result);
- if (error != kSuccess) {
- LOG(INFO) << "Javascript failed to execute" << std::endl;
- return false;
- }
-
- if (result == NULL || result->GetType() != Value::TYPE_DICTIONARY) {
- LOG(ERROR) << "Expected JavaScript atom to return a dictionary";
- return false;
- }
-
- DictionaryValue* dict = static_cast<DictionaryValue*>(result);
- return dict->GetInteger("x", x) && dict->GetInteger("y", y);
-}
-
-bool WebElementCommand::GetElementSize(int* width, int* height) {
- scoped_ptr<ListValue> args(new ListValue());
- Value* result = NULL;
-
- std::string jscript = build_atom(GET_SIZE, sizeof GET_LOCATION);
- args->Append(GetElementIdAsDictionaryValue(element_id));
-
- ErrorCode error = session_->ExecuteScript(jscript, args.get(), &result);
- if (error != kSuccess) {
- LOG(ERROR) << "Javascript failed to execute" << std::endl;
- return false;
- }
-
- if (result == NULL || result->GetType() != Value::TYPE_DICTIONARY) {
- LOG(ERROR) << "Expected JavaScript atom to return "
- << "{width:number, height:number} dictionary." << std::endl;
- return false;
- }
-
- DictionaryValue* dict = static_cast<DictionaryValue*>(result);
- return dict->GetInteger("width", width) &&
- dict->GetInteger("height", height);
-}
-
-} // namespace webdriver
diff --git a/chrome/test/webdriver/commands/webelement_command.h b/chrome/test/webdriver/commands/webelement_command.h
deleted file mode 100644
index 79e3a05..0000000
--- a/chrome/test/webdriver/commands/webelement_command.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMAND_H_
-#define CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMAND_H_
-
-#include <string>
-#include <vector>
-
-#include "chrome/test/webdriver/commands/webdriver_command.h"
-
-namespace webdriver {
-
-// Handles commands that interact with a web element in the WebDriver REST
-// service.
-class WebElementCommand : public WebDriverCommand {
- public:
- inline WebElementCommand(const std::vector<std::string>& path_segments,
- const DictionaryValue* const parameters)
- : WebDriverCommand(path_segments, parameters),
- path_segments_(path_segments) {}
- virtual ~WebElementCommand() {}
-
- virtual bool Init(Response* const response);
-
- protected:
- bool GetElementLocation(bool in_view, int* x, int* y);
- bool GetElementSize(int* width, int* height);
-
- const std::vector<std::string>& path_segments_;
- std::string element_id;
-
- private:
- virtual bool RequiresValidTab() { return true; }
-
- DISALLOW_COPY_AND_ASSIGN(WebElementCommand);
-};
-
-} // namespace webdriver
-
-#endif // CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMAND_H_
diff --git a/chrome/test/webdriver/commands/webelement_commands.cc b/chrome/test/webdriver/commands/webelement_commands.cc
new file mode 100644
index 0000000..e9bb7c47
--- /dev/null
+++ b/chrome/test/webdriver/commands/webelement_commands.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/webdriver/commands/webelement_commands.h"
+
+#include "base/scoped_ptr.h"
+#include "base/third_party/icu/icu_utf.h"
+#include "base/values.h"
+#include "chrome/test/webdriver/commands/response.h"
+#include "chrome/test/webdriver/error_codes.h"
+#include "chrome/test/webdriver/session.h"
+#include "chrome/test/webdriver/utility_functions.h"
+#include "third_party/webdriver/atoms.h"
+
+namespace webdriver {
+
+bool WebElementCommand::Init(Response* const response) {
+ if (!WebDriverCommand::Init(response))
+ return false;
+
+ // There should be at least 5 segments to match
+ // "/session/$session/element/$id"
+ if (path_segments_.size() < 5) {
+ SET_WEBDRIVER_ERROR(response, "Path segments is less than 5",
+ kBadRequest);
+ return false;
+ }
+
+ // We cannot verify the ID is valid until we execute the command and
+ // inject the ID into the in-page cache.
+ element_id = path_segments_.at(4);
+ return true;
+}
+
+bool WebElementCommand::GetElementLocation(bool in_view, int* x, int* y) {
+ scoped_ptr<ListValue> args(new ListValue());
+ Value* result = NULL;
+
+ std::string jscript = build_atom(GET_LOCATION, sizeof GET_LOCATION);
+ if (in_view) {
+ jscript.append("arguments[0].scrollIntoView();");
+ }
+ jscript.append("return getLocation(arguments[0]);");
+
+ args->Append(GetElementIdAsDictionaryValue(element_id));
+
+ ErrorCode error = session_->ExecuteScript(jscript, args.get(), &result);
+ if (error != kSuccess) {
+ LOG(INFO) << "Javascript failed to execute" << std::endl;
+ return false;
+ }
+
+ if (result == NULL || result->GetType() != Value::TYPE_DICTIONARY) {
+ LOG(ERROR) << "Expected JavaScript atom to return a dictionary";
+ return false;
+ }
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(result);
+ return dict->GetInteger("x", x) && dict->GetInteger("y", y);
+}
+
+bool WebElementCommand::GetElementSize(int* width, int* height) {
+ scoped_ptr<ListValue> args(new ListValue());
+ Value* result = NULL;
+
+ std::string jscript = build_atom(GET_SIZE, sizeof GET_LOCATION);
+ args->Append(GetElementIdAsDictionaryValue(element_id));
+
+ ErrorCode error = session_->ExecuteScript(jscript, args.get(), &result);
+ if (error != kSuccess) {
+ LOG(ERROR) << "Javascript failed to execute" << std::endl;
+ return false;
+ }
+
+ if (result == NULL || result->GetType() != Value::TYPE_DICTIONARY) {
+ LOG(ERROR) << "Expected JavaScript atom to return "
+ << "{width:number, height:number} dictionary." << std::endl;
+ return false;
+ }
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(result);
+ return dict->GetInteger("width", width) &&
+ dict->GetInteger("height", height);
+}
+
+void ElementValueCommand::ExecuteGet(Response* const response) {
+ Value* unscoped_result = NULL;
+ ListValue args;
+ std::string script = "return arguments[0]['value']";
+ args.Append(GetElementIdAsDictionaryValue(element_id));
+ ErrorCode code =
+ session_->ExecuteScript(script, &args, &unscoped_result);
+ scoped_ptr<Value> result(unscoped_result);
+ if (code != kSuccess) {
+ SET_WEBDRIVER_ERROR(response, "Failed to execute script", code);
+ return;
+ }
+ if (!result->IsType(Value::TYPE_STRING) &&
+ !result->IsType(Value::TYPE_NULL)) {
+ SET_WEBDRIVER_ERROR(response,
+ "Result is not string or null type",
+ kInternalServerError);
+ return;
+ }
+ response->set_status(kSuccess);
+ response->set_value(result.release());
+}
+
+void ElementValueCommand::ExecutePost(Response* const response) {
+ ListValue* key_list;
+ if (!GetListParameter("value", &key_list)) {
+ SET_WEBDRIVER_ERROR(response,
+ "Missing or invalid 'value' parameter",
+ kBadRequest);
+ return;
+ }
+ // Flatten the given array of strings into one.
+ string16 keys;
+ for (size_t i = 0; i < key_list->GetSize(); ++i) {
+ string16 keys_list_part;
+ key_list->GetString(i, &keys_list_part);
+ for (size_t j = 0; j < keys_list_part.size(); ++j) {
+ if (CBU16_IS_SURROGATE(keys_list_part[j])) {
+ SET_WEBDRIVER_ERROR(
+ response,
+ "ChromeDriver only supports characters in the BMP",
+ kBadRequest);
+ return;
+ }
+ }
+ keys.append(keys_list_part);
+ }
+
+ ErrorCode code =
+ session_->SendKeys(GetElementIdAsDictionaryValue(element_id), keys);
+ if (code != kSuccess) {
+ SET_WEBDRIVER_ERROR(response,
+ "Internal SendKeys error",
+ code);
+ return;
+ }
+ response->set_status(kSuccess);
+}
+
+void ElementTextCommand::ExecuteGet(Response* const response) {
+ Value* unscoped_result = NULL;
+ ListValue args;
+ // TODO(jleyba): Use a real javascript atom.
+ std::string script =
+ "function getText(element) {"
+ " if (element instanceof Text) {"
+ " return element.textContent.replace(/^\\s+|\\s+$/g, '');"
+ " }"
+ " var childrenText = '';"
+ " for (var i = 0; i < element.childNodes.length; i++) {"
+ " childrenText += getText(element.childNodes[i]);"
+ " }"
+ " return childrenText;"
+ "}"
+ "return getText(arguments[0]);";
+ args.Append(GetElementIdAsDictionaryValue(element_id));
+ ErrorCode code =
+ session_->ExecuteScript(script, &args, &unscoped_result);
+ scoped_ptr<Value> result(unscoped_result);
+ if (code != kSuccess) {
+ SET_WEBDRIVER_ERROR(response, "Failed to execute script", code);
+ return;
+ }
+ if (!result->IsType(Value::TYPE_STRING)) {
+ SET_WEBDRIVER_ERROR(response,
+ "Result is not string type",
+ kInternalServerError);
+ return;
+ }
+ response->set_status(kSuccess);
+ response->set_value(result.release());
+}
+
+} // namespace webdriver
diff --git a/chrome/test/webdriver/commands/webelement_commands.h b/chrome/test/webdriver/commands/webelement_commands.h
new file mode 100644
index 0000000..ec5d6b7
--- /dev/null
+++ b/chrome/test/webdriver/commands/webelement_commands.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMANDS_H_
+#define CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMANDS_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/test/webdriver/commands/webdriver_command.h"
+
+class DictionaryValue;
+
+namespace webdriver {
+
+class Response;
+
+// Handles commands that interact with a web element in the WebDriver REST
+// service.
+class WebElementCommand : public WebDriverCommand {
+ public:
+ inline WebElementCommand(const std::vector<std::string>& path_segments,
+ const DictionaryValue* const parameters)
+ : WebDriverCommand(path_segments, parameters),
+ path_segments_(path_segments) {}
+ virtual ~WebElementCommand() {}
+
+ virtual bool Init(Response* const response);
+
+ protected:
+ bool GetElementLocation(bool in_view, int* x, int* y);
+ bool GetElementSize(int* width, int* height);
+
+ const std::vector<std::string>& path_segments_;
+ std::string element_id;
+
+ private:
+ virtual bool RequiresValidTab() { return true; }
+
+ DISALLOW_COPY_AND_ASSIGN(WebElementCommand);
+};
+
+// Sends keys to the specified web element. Also gets the value property of an
+// element.
+// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
+class ElementValueCommand : public WebElementCommand {
+ public:
+ ElementValueCommand(const std::vector<std::string>& path_segments,
+ DictionaryValue* parameters)
+ : WebElementCommand(path_segments, parameters) {}
+ virtual ~ElementValueCommand() {}
+
+ virtual bool DoesGet() { return true; }
+ virtual bool DoesPost() { return true; }
+ virtual void ExecuteGet(Response* const response);
+ virtual void ExecutePost(Response* const response);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ElementValueCommand);
+};
+
+// Gets the visible text of the specified web element.
+// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/text
+class ElementTextCommand : public WebElementCommand {
+ public:
+ ElementTextCommand(const std::vector<std::string>& path_segments,
+ DictionaryValue* parameters)
+ : WebElementCommand(path_segments, parameters) {}
+ virtual ~ElementTextCommand() {}
+
+ virtual bool DoesGet() { return true; }
+ virtual void ExecuteGet(Response* const response);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ElementTextCommand);
+};
+
+} // namespace webdriver
+
+#endif // CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMANDS_H_
diff --git a/chrome/test/webdriver/server.cc b/chrome/test/webdriver/server.cc
index 0cd63aa..c24242b 100644
--- a/chrome/test/webdriver/server.cc
+++ b/chrome/test/webdriver/server.cc
@@ -39,7 +39,7 @@
#include "chrome/test/webdriver/commands/speed_command.h"
#include "chrome/test/webdriver/commands/title_command.h"
#include "chrome/test/webdriver/commands/url_command.h"
-
+#include "chrome/test/webdriver/commands/webelement_commands.h"
#include "third_party/mongoose/mongoose.h"
// Make sure we have ho zombies from CGIs.
@@ -94,11 +94,14 @@ void InitCallbacks(struct mg_context* ctx,
SetCallback<FindManyElementsCommand>(ctx, "/session/*/elements");
SetCallback<FindOneElementCommand>(ctx, "/session/*/element/*/element");
SetCallback<FindManyElementsCommand>(ctx, "/session/*/elements/*/elements");
+ SetCallback<ElementValueCommand>(ctx, "/session/*/element/*/value");
+ SetCallback<ElementTextCommand>(ctx, "/session/*/element/*/text");
// Since the /session/* is a wild card that would match the above URIs, this
// line MUST be the last registered URI with the server.
SetCallback<SessionWithID>(ctx, "/session/*");
}
+
} // namespace webdriver
// Configures mongoose according to the given command line flags.
diff --git a/chrome/test/webdriver/session.cc b/chrome/test/webdriver/session.cc
index 00de26c..665726e 100644
--- a/chrome/test/webdriver/session.cc
+++ b/chrome/test/webdriver/session.cc
@@ -22,6 +22,7 @@
#include "chrome/common/chrome_switches.h"
#include "chrome/test/test_launcher_utils.h"
#include "chrome/test/webdriver/utility_functions.h"
+#include "chrome/test/webdriver/webdriver_key_converter.h"
#include "third_party/webdriver/atoms.h"
namespace webdriver {
@@ -127,6 +128,28 @@ ErrorCode Session::ExecuteScript(const std::string& script,
return static_cast<ErrorCode>(status);
}
+ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) {
+ ListValue args;
+ args.Append(element);
+ // TODO(jleyba): Update this to use the correct atom.
+ std::string script = "document.activeElement.blur();arguments[0].focus();";
+ Value* unscoped_result = NULL;
+ ErrorCode code = ExecuteScript(script, &args, &unscoped_result);
+ scoped_ptr<Value> result(unscoped_result);
+ if (code != kSuccess)
+ return code;
+
+ bool success = false;
+ RunSessionTask(NewRunnableMethod(
+ this,
+ &Session::SendKeysOnSessionThread,
+ keys,
+ &success));
+ if (!success)
+ return kUnknownError;
+ return kSuccess;
+}
+
bool Session::NavigateToURL(const std::string& url) {
bool success = false;
RunSessionTask(NewRunnableMethod(
@@ -211,4 +234,24 @@ void Session::TerminateOnSessionThread() {
automation_.reset();
}
+void Session::SendKeysOnSessionThread(const string16& keys,
+ bool* success) {
+ *success = true;
+ std::vector<WebKeyEvent> key_events;
+ ConvertKeysToWebKeyEvents(keys, &key_events);
+ for (size_t i = 0; i < key_events.size(); ++i) {
+ bool key_success = false;
+ automation_->SendWebKeyEvent(key_events[i], &key_success);
+ if (!key_success) {
+ LOG(ERROR) << "Failed to send key event. Event details:\n"
+ << "Type: " << key_events[i].type << "\n"
+ << "KeyCode: " << key_events[i].key_code << "\n"
+ << "UnmodifiedText: " << key_events[i].unmodified_text << "\n"
+ << "ModifiedText: " << key_events[i].modified_text << "\n"
+ << "Modifiers: " << key_events[i].modifiers << "\n";
+ *success = false;
+ }
+ }
+}
+
} // namespace webdriver
diff --git a/chrome/test/webdriver/session.h b/chrome/test/webdriver/session.h
index 8bc5cd5..abd8021 100644
--- a/chrome/test/webdriver/session.h
+++ b/chrome/test/webdriver/session.h
@@ -8,6 +8,8 @@
#include <string>
#include "base/scoped_ptr.h"
+#include "base/string16.h"
+#include "base/values.h"
#include "chrome/test/webdriver/automation.h"
#include "chrome/test/webdriver/error_codes.h"
@@ -46,6 +48,9 @@ class Session {
const ListValue* const args,
Value** value);
+ // Send the given keys to the given element dictionary. This function takes
+ // ownership of |element|.
+ ErrorCode SendKeys(DictionaryValue* element, const string16& keys);
bool NavigateToURL(const std::string& url);
bool GoForward();
@@ -82,6 +87,7 @@ class Session {
base::WaitableEvent* done_event);
void InitOnSessionThread(bool* success);
void TerminateOnSessionThread();
+ void SendKeysOnSessionThread(const string16& keys, bool* success);
scoped_ptr<Automation> automation_;
base::Thread thread_;
diff --git a/chrome/test/webdriver/webdriver_key_converter.cc b/chrome/test/webdriver/webdriver_key_converter.cc
new file mode 100644
index 0000000..3f01ed8
--- /dev/null
+++ b/chrome/test/webdriver/webdriver_key_converter.cc
@@ -0,0 +1,279 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/webdriver/webdriver_key_converter.h"
+
+#include "base/utf_string_conversions.h"
+#include "chrome/common/automation_constants.h"
+
+namespace {
+
+// TODO(kkania): Use this in KeyMap.
+// Ordered list of all the key codes corresponding to special WebDriver keys.
+// These WebDriver keys are defined in the Unicode Private Use Area.
+// http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value
+const ui::KeyboardCode kSpecialWebDriverKeys[] = {
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_HELP,
+ ui::VKEY_BACK,
+ ui::VKEY_TAB,
+ ui::VKEY_CLEAR,
+ ui::VKEY_RETURN,
+ ui::VKEY_RETURN,
+ ui::VKEY_SHIFT,
+ ui::VKEY_CONTROL,
+ ui::VKEY_MENU,
+ ui::VKEY_PAUSE,
+ ui::VKEY_ESCAPE,
+ ui::VKEY_SPACE,
+ ui::VKEY_PRIOR, // page up
+ ui::VKEY_NEXT, // page down
+ ui::VKEY_END,
+ ui::VKEY_HOME,
+ ui::VKEY_LEFT,
+ ui::VKEY_UP,
+ ui::VKEY_RIGHT,
+ ui::VKEY_DOWN,
+ ui::VKEY_INSERT,
+ ui::VKEY_DELETE,
+ ui::VKEY_OEM_1, // semicolon
+ ui::VKEY_OEM_PLUS, // equals
+ ui::VKEY_NUMPAD0,
+ ui::VKEY_NUMPAD1,
+ ui::VKEY_NUMPAD2,
+ ui::VKEY_NUMPAD3,
+ ui::VKEY_NUMPAD4,
+ ui::VKEY_NUMPAD5,
+ ui::VKEY_NUMPAD6,
+ ui::VKEY_NUMPAD7,
+ ui::VKEY_NUMPAD8,
+ ui::VKEY_NUMPAD9,
+ ui::VKEY_MULTIPLY,
+ ui::VKEY_ADD,
+ ui::VKEY_OEM_COMMA,
+ ui::VKEY_SUBTRACT,
+ ui::VKEY_DECIMAL,
+ ui::VKEY_DIVIDE,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_UNKNOWN,
+ ui::VKEY_F1,
+ ui::VKEY_F2,
+ ui::VKEY_F3,
+ ui::VKEY_F4,
+ ui::VKEY_F5,
+ ui::VKEY_F6,
+ ui::VKEY_F7,
+ ui::VKEY_F8,
+ ui::VKEY_F9,
+ ui::VKEY_F10,
+ ui::VKEY_F11,
+ ui::VKEY_F12};
+
+const char16 kWebDriverNullKey = 0xE000U;
+const char16 kWebDriverShiftKey = 0xE008U;
+const char16 kWebDriverControlKey = 0xE009U;
+const char16 kWebDriverAltKey = 0xE00AU;
+const char16 kWebDriverCommandKey = 0xE03DU;
+
+// Returns whether the given key is a WebDriver key modifier.
+bool IsModifierKey(char16 key) {
+ switch (key) {
+ case kWebDriverShiftKey:
+ case kWebDriverControlKey:
+ case kWebDriverAltKey:
+ case kWebDriverCommandKey:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Gets the key code associated with |key|, if it is a special WebDriver key.
+// Returns whether |key| is a special WebDriver key. If true, |key_code| will
+// be set.
+bool KeyCodeFromSpecialWebDriverKey(char16 key, ui::KeyboardCode* key_code) {
+ int index = static_cast<int>(key) - 0xE000U;
+ bool is_special_key = index >= 0 &&
+ index < static_cast<int>(arraysize(kSpecialWebDriverKeys));
+ if (is_special_key)
+ *key_code = kSpecialWebDriverKeys[index];
+ return is_special_key;
+}
+
+// Converts a character to the key code and modifier set that would
+// produce the character using the given keyboard layout.
+bool ConvertCharToKeyCode(
+ char16 key, ui::KeyboardCode* key_code, int *necessary_modifiers) {
+#if defined(OS_WIN)
+ short vkey_and_modifiers = ::VkKeyScanW(key);
+ bool translated = vkey_and_modifiers != -1 &&
+ LOBYTE(vkey_and_modifiers) != -1 &&
+ HIBYTE(vkey_and_modifiers) != -1;
+ if (translated) {
+ *key_code = static_cast<ui::KeyboardCode>(LOBYTE(vkey_and_modifiers));
+ *necessary_modifiers = HIBYTE(vkey_and_modifiers);
+ }
+ return translated;
+#else
+ // TODO(kkania): Implement.
+ return false;
+#endif
+}
+
+// Returns the character that would be produced from the given key code and
+// modifier set, or "" if no character would be produced.
+std::string ConvertKeyCodeToText(ui::KeyboardCode key_code, int modifiers) {
+#if defined(OS_WIN)
+ UINT scan_code = ::MapVirtualKeyW(key_code, MAPVK_VK_TO_VSC);
+ BYTE keyboard_state[256];
+ ::GetKeyboardState(keyboard_state);
+ if (modifiers & automation::kShiftKeyMask)
+ keyboard_state[VK_SHIFT] |= 0x80;
+ wchar_t chars[5];
+ int code = ::ToUnicode(key_code, scan_code, keyboard_state, chars, 4, 0);
+ if (code <= 0) {
+ return "";
+ } else {
+ std::string text;
+ WideToUTF8(chars, code, &text);
+ return text;
+ }
+#else
+ // TODO(kkania): Implement
+ return "";
+#endif
+}
+
+} // namespace
+
+namespace webdriver {
+
+WebKeyEvent CreateKeyDownEvent(ui::KeyboardCode key_code, int modifiers) {
+ return WebKeyEvent(automation::kRawKeyDownType, key_code, "", "", modifiers);
+}
+
+WebKeyEvent CreateKeyUpEvent(ui::KeyboardCode key_code, int modifiers) {
+ return WebKeyEvent(automation::kKeyUpType, key_code, "", "", modifiers);
+}
+
+WebKeyEvent CreateCharEvent(const std::string& unmodified_text,
+ const std::string& modified_text,
+ int modifiers) {
+ return WebKeyEvent(automation::kCharType,
+ ui::VKEY_UNKNOWN,
+ unmodified_text,
+ modified_text,
+ modifiers);
+}
+
+void ConvertKeysToWebKeyEvents(const string16& client_keys,
+ std::vector<WebKeyEvent>* key_events) {
+ // Add an implicit NULL character to the end of the input to depress all
+ // modifiers.
+ string16 keys = client_keys;
+ keys.push_back(kWebDriverNullKey);
+
+ int sticky_modifiers = 0;
+ for (size_t i = 0; i < keys.size(); ++i) {
+ char16 key = keys[i];
+
+ if (key == kWebDriverNullKey) {
+ // Release all modifier keys and clear |stick_modifiers|.
+ if (sticky_modifiers & automation::kShiftKeyMask)
+ key_events->push_back(CreateKeyUpEvent(ui::VKEY_SHIFT, 0));
+ if (sticky_modifiers & automation::kControlKeyMask)
+ key_events->push_back(CreateKeyUpEvent(ui::VKEY_CONTROL, 0));
+ if (sticky_modifiers & automation::kAltKeyMask)
+ key_events->push_back(CreateKeyUpEvent(ui::VKEY_MENU, 0));
+ if (sticky_modifiers & automation::kMetaKeyMask)
+ key_events->push_back(CreateKeyUpEvent(ui::VKEY_COMMAND, 0));
+ sticky_modifiers = 0;
+ continue;
+ }
+ if (IsModifierKey(key)) {
+ // Press or release the modifier, and adjust |sticky_modifiers|.
+ bool modifier_down = false;
+ ui::KeyboardCode key_code;
+ if (key == kWebDriverShiftKey) {
+ sticky_modifiers ^= automation::kShiftKeyMask;
+ modifier_down = sticky_modifiers & automation::kShiftKeyMask;
+ key_code = ui::VKEY_SHIFT;
+ } else if (key == kWebDriverControlKey) {
+ sticky_modifiers ^= automation::kControlKeyMask;
+ modifier_down = sticky_modifiers & automation::kControlKeyMask;
+ key_code = ui::VKEY_CONTROL;
+ } else if (key == kWebDriverAltKey) {
+ sticky_modifiers ^= automation::kAltKeyMask;
+ modifier_down = sticky_modifiers & automation::kAltKeyMask;
+ key_code = ui::VKEY_MENU;
+ } else if (key == kWebDriverCommandKey) {
+ sticky_modifiers ^= automation::kMetaKeyMask;
+ modifier_down = sticky_modifiers & automation::kMetaKeyMask;
+ key_code = ui::VKEY_COMMAND;
+ }
+ if (modifier_down)
+ key_events->push_back(CreateKeyDownEvent(key_code, sticky_modifiers));
+ else
+ key_events->push_back(CreateKeyUpEvent(key_code, sticky_modifiers));
+ continue;
+ }
+
+ ui::KeyboardCode key_code = ui::VKEY_UNKNOWN;
+ std::string unmodified_text, modified_text;
+ int all_modifiers = sticky_modifiers;
+
+ bool is_special_key = KeyCodeFromSpecialWebDriverKey(key, &key_code);
+ if (is_special_key && key_code == ui::VKEY_UNKNOWN) {
+ LOG(ERROR) << "Unknown WebDriver key: " << static_cast<int>(key);
+ continue;
+ }
+ if (!is_special_key) {
+ int necessary_modifiers = 0;
+ ConvertCharToKeyCode(key, &key_code, &necessary_modifiers);
+ all_modifiers |= necessary_modifiers;
+ }
+ if (key_code != ui::VKEY_UNKNOWN) {
+ unmodified_text = ConvertKeyCodeToText(key_code, 0);
+ modified_text = ConvertKeyCodeToText(key_code, all_modifiers);
+ }
+ if (!is_special_key && (unmodified_text.empty() || modified_text.empty())) {
+ // Do a best effort and use the raw key we were given.
+ LOG(WARNING) << "No translation for key code. Code point: "
+ << static_cast<int>(key);
+ if (unmodified_text.empty())
+ unmodified_text = UTF16ToUTF8(keys.substr(i, 1));
+ if (modified_text.empty())
+ modified_text = UTF16ToUTF8(keys.substr(i, 1));
+ }
+
+ // Create the key events.
+ bool need_shift_key =
+ all_modifiers & automation::kShiftKeyMask &&
+ !(sticky_modifiers & automation::kShiftKeyMask);
+ if (need_shift_key) {
+ key_events->push_back(
+ CreateKeyDownEvent(ui::VKEY_SHIFT, sticky_modifiers));
+ }
+
+ key_events->push_back(CreateKeyDownEvent(key_code, all_modifiers));
+ if (unmodified_text.length() || modified_text.length()) {
+ key_events->push_back(
+ CreateCharEvent(unmodified_text, modified_text, all_modifiers));
+ }
+ key_events->push_back(CreateKeyUpEvent(key_code, all_modifiers));
+
+ if (need_shift_key) {
+ key_events->push_back(
+ CreateKeyUpEvent(ui::VKEY_SHIFT, sticky_modifiers));
+ }
+ }
+}
+
+} // namespace webdriver
diff --git a/chrome/test/webdriver/webdriver_key_converter.h b/chrome/test/webdriver/webdriver_key_converter.h
new file mode 100644
index 0000000..e0fad87
--- /dev/null
+++ b/chrome/test/webdriver/webdriver_key_converter.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_TEST_WEBDRIVER_WEBDRIVER_KEY_CONVERTER_H_
+#define CHROME_TEST_WEBDRIVER_WEBDRIVER_KEY_CONVERTER_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/string16.h"
+#include "chrome/test/webdriver/automation.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace webdriver {
+
+// Convenience functions for creating |WebKeyEvent|s. Used by unittests.
+WebKeyEvent CreateKeyDownEvent(ui::KeyboardCode key_code, int modifiers);
+WebKeyEvent CreateKeyUpEvent(ui::KeyboardCode key_code, int modifiers);
+WebKeyEvent CreateCharEvent(const std::string& unmodified_text,
+ const std::string& modified_text,
+ int modifiers);
+
+// Converts keys into appropriate |WebKeyEvent|s.
+void ConvertKeysToWebKeyEvents(const string16& keys,
+ std::vector<WebKeyEvent>* key_events);
+
+} // namespace webdriver
+
+#endif // CHROME_TEST_WEBDRIVER_WEBDRIVER_KEY_CONVERTER_H_
diff --git a/chrome/test/webdriver/webdriver_key_converter_unittest.cc b/chrome/test/webdriver/webdriver_key_converter_unittest.cc
new file mode 100644
index 0000000..47a3afb
--- /dev/null
+++ b/chrome/test/webdriver/webdriver_key_converter_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/test/webdriver/automation.h"
+#include "chrome/test/webdriver/webdriver_key_converter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webdriver {
+
+void CheckEvents(const string16& keys,
+ WebKeyEvent expected_events[],
+ size_t expected_size) {
+ std::vector<WebKeyEvent> events;
+ ConvertKeysToWebKeyEvents(keys, &events);
+ EXPECT_EQ(expected_size, events.size());
+ for (size_t i = 0; i < events.size() && i < expected_size; ++i) {
+ EXPECT_EQ(expected_events[i].type, events[i].type);
+ EXPECT_EQ(expected_events[i].key_code, events[i].key_code);
+ EXPECT_EQ(expected_events[i].unmodified_text, events[i].unmodified_text);
+ EXPECT_EQ(expected_events[i].modified_text, events[i].modified_text);
+ EXPECT_EQ(expected_events[i].modifiers, events[i].modifiers);
+ }
+}
+
+void CheckEvents(const std::string& keys,
+ WebKeyEvent expected_events[],
+ size_t expected_size) {
+ CheckEvents(UTF8ToUTF16(keys), expected_events, expected_size);
+}
+
+TEST(WebDriverKeyConverter, SingleChar) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_H, 0),
+ CreateCharEvent("h", "h", 0),
+ CreateKeyUpEvent(ui::VKEY_H, 0)};
+ CheckEvents("h", event_array, arraysize(event_array));
+}
+
+TEST(WebDriverKeyConverter, MultipleChars) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_H, 0),
+ CreateCharEvent("h", "h", 0),
+ CreateKeyUpEvent(ui::VKEY_H, 0),
+ CreateKeyDownEvent(ui::VKEY_E, 0),
+ CreateCharEvent("e", "e", 0),
+ CreateKeyUpEvent(ui::VKEY_E, 0),
+ CreateKeyDownEvent(ui::VKEY_Y, 0),
+ CreateCharEvent("y", "y", 0),
+ CreateKeyUpEvent(ui::VKEY_Y, 0)};
+ CheckEvents("hey", event_array, arraysize(event_array));
+}
+
+TEST(WebDriverKeyConverter, WebDriverSpecialChar) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_SPACE, 0),
+ CreateCharEvent(" ", " ", 0),
+ CreateKeyUpEvent(ui::VKEY_SPACE, 0)};
+ string16 keys;
+ keys.push_back(static_cast<char16>(0xE00DU));
+ CheckEvents(keys, event_array, arraysize(event_array));
+}
+
+TEST(WebDriverKeyConverter, WebDriverSpecialNonCharKey) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_F1, 0),
+ CreateKeyUpEvent(ui::VKEY_F1, 0)};
+ string16 keys;
+ keys.push_back(static_cast<char16>(0xE031U));
+ CheckEvents(keys, event_array, arraysize(event_array));
+}
+
+TEST(WebDriverKeyConverter, FrenchKeyOnEnglishLayout) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_UNKNOWN, 0),
+ CreateCharEvent(WideToUTF8(L"\u00E9"), WideToUTF8(L"\u00E9"), 0),
+ CreateKeyUpEvent(ui::VKEY_UNKNOWN, 0)};
+ CheckEvents(WideToUTF16(L"\u00E9"), event_array, arraysize(event_array));
+}
+
+#if defined(OS_WIN)
+TEST(WebDriverKeyConverter, FrenchKeyOnFrenchLayout) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_2, 0),
+ CreateCharEvent(WideToUTF8(L"\u00E9"), WideToUTF8(L"\u00E9"), 0),
+ CreateKeyUpEvent(ui::VKEY_2, 0)};
+ HKL french_layout = ::LoadKeyboardLayout(L"0000040C", 0);
+ ASSERT_TRUE(french_layout);
+ HKL prev_layout = ::ActivateKeyboardLayout(french_layout, 0);
+ CheckEvents(WideToUTF16(L"\u00E9"), event_array, arraysize(event_array));
+ ::ActivateKeyboardLayout(prev_layout, 0);
+}
+#endif
+
+TEST(WebDriverKeyConverter, UppercaseCharDoesShift) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_SHIFT, 0),
+ CreateKeyDownEvent(ui::VKEY_A, automation::kShiftKeyMask),
+ CreateCharEvent("a", "A", automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_A, automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_SHIFT, 0)};
+ CheckEvents("A", event_array, arraysize(event_array));
+}
+
+TEST(WebDriverKeyConverter, UppercaseCharUsesShiftOnlyIfNecessary) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_SHIFT, automation::kShiftKeyMask),
+ CreateKeyDownEvent(ui::VKEY_A, automation::kShiftKeyMask),
+ CreateCharEvent("a", "A", automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_A, automation::kShiftKeyMask),
+ CreateKeyDownEvent(ui::VKEY_B, automation::kShiftKeyMask),
+ CreateCharEvent("b", "B", automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_B, automation::kShiftKeyMask),
+ CreateKeyDownEvent(ui::VKEY_C, automation::kShiftKeyMask),
+ CreateCharEvent("c", "C", automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_C, automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_SHIFT, 0)};
+ string16 keys;
+ keys.push_back(static_cast<char16>(0xE008U));
+ keys.append(UTF8ToUTF16("aBc"));
+ CheckEvents(keys, event_array, arraysize(event_array));
+}
+
+TEST(WebDriverKeyConverter, ToggleModifiers) {
+ WebKeyEvent event_array[] = {
+ CreateKeyDownEvent(ui::VKEY_SHIFT, automation::kShiftKeyMask),
+ CreateKeyUpEvent(ui::VKEY_SHIFT, 0),
+ CreateKeyDownEvent(ui::VKEY_CONTROL, automation::kControlKeyMask),
+ CreateKeyUpEvent(ui::VKEY_CONTROL, 0),
+ CreateKeyDownEvent(ui::VKEY_MENU, automation::kAltKeyMask),
+ CreateKeyUpEvent(ui::VKEY_MENU, 0),
+ CreateKeyDownEvent(ui::VKEY_COMMAND, automation::kMetaKeyMask),
+ CreateKeyUpEvent(ui::VKEY_COMMAND, 0)};
+ string16 keys;
+ keys.push_back(static_cast<char16>(0xE008U));
+ keys.push_back(static_cast<char16>(0xE008U));
+ keys.push_back(static_cast<char16>(0xE009U));
+ keys.push_back(static_cast<char16>(0xE009U));
+ keys.push_back(static_cast<char16>(0xE00AU));
+ keys.push_back(static_cast<char16>(0xE00AU));
+ keys.push_back(static_cast<char16>(0xE03DU));
+ keys.push_back(static_cast<char16>(0xE03DU));
+ CheckEvents(keys, event_array, arraysize(event_array));
+}
+
+} // namespace webdriver