diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-13 03:11:21 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-13 03:11:21 +0000 |
commit | 7cc2c91be249eff44e492eea4ffc095abc4e0b59 (patch) | |
tree | cc58213c6285a91254729fefdbb26dbebdf9d795 | |
parent | 8f7c7cd0eb53a8336a2db1d2b90818305e76706b (diff) | |
download | chromium_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.gypi | 8 | ||||
-rw-r--r-- | chrome/test/automation/browser_proxy.cc | 8 | ||||
-rw-r--r-- | chrome/test/webdriver/WEBDRIVER_TESTS | 4 | ||||
-rw-r--r-- | chrome/test/webdriver/automation.cc | 22 | ||||
-rw-r--r-- | chrome/test/webdriver/automation.h | 25 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/command.cc | 5 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/command.h | 4 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/webelement_command.cc | 85 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/webelement_command.h | 42 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/webelement_commands.cc | 180 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/webelement_commands.h | 81 | ||||
-rw-r--r-- | chrome/test/webdriver/server.cc | 5 | ||||
-rw-r--r-- | chrome/test/webdriver/session.cc | 43 | ||||
-rw-r--r-- | chrome/test/webdriver/session.h | 6 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_key_converter.cc | 279 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_key_converter.h | 31 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_key_converter_unittest.cc | 151 |
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 |