| Index: chrome/test/webdriver/session.cc
|
| diff --git a/chrome/test/webdriver/session.cc b/chrome/test/webdriver/session.cc
|
| index bfbefeb8055825f8222388531314e153044591b1..849e7c607a8fbc95388c2ca326a194e6cd1b6447 100644
|
| --- a/chrome/test/webdriver/session.cc
|
| +++ b/chrome/test/webdriver/session.cc
|
| @@ -12,7 +12,9 @@
|
| #include "base/message_loop_proxy.h"
|
| #include "base/process.h"
|
| #include "base/process_util.h"
|
| +#include "base/scoped_ptr.h"
|
| #include "base/stringprintf.h"
|
| +#include "base/string_number_conversions.h"
|
| #include "base/string_util.h"
|
| #include "base/json/json_reader.h"
|
| #include "base/json/json_writer.h"
|
| @@ -22,6 +24,7 @@
|
| #include "chrome/common/chrome_constants.h"
|
| #include "chrome/common/chrome_switches.h"
|
| #include "chrome/test/test_launcher_utils.h"
|
| +#include "chrome/test/webdriver/session_manager.h"
|
| #include "chrome/test/webdriver/utility_functions.h"
|
| #include "chrome/test/webdriver/webdriver_key_converter.h"
|
| #include "googleurl/src/gurl.h"
|
| @@ -29,27 +32,31 @@
|
|
|
| namespace webdriver {
|
|
|
| -Session::Session(const std::string& id)
|
| - : thread_(id.c_str()),
|
| - id_(id),
|
| - window_num_(0),
|
| +Session::Session()
|
| + : id_(GenerateRandomID()),
|
| + thread_(id_.c_str()),
|
| implicit_wait_(0),
|
| - current_frame_xpath_("") {
|
| + current_frame_xpath_(""),
|
| + current_window_id_(0) {
|
| + SessionManager::GetInstance()->Add(this);
|
| }
|
|
|
| -Session::~Session() {}
|
| +Session::~Session() {
|
| + SessionManager::GetInstance()->Remove(id_);
|
| +}
|
|
|
| bool Session::Init() {
|
| - if (!thread_.Start()) {
|
| + bool success = false;
|
| + if (thread_.Start()) {
|
| + RunSessionTask(NewRunnableMethod(
|
| + this,
|
| + &Session::InitOnSessionThread,
|
| + &success));
|
| + } else {
|
| LOG(ERROR) << "Cannot start session thread";
|
| - return false;
|
| }
|
| -
|
| - bool success = false;
|
| - RunSessionTask(NewRunnableMethod(
|
| - this,
|
| - &Session::InitOnSessionThread,
|
| - &success));
|
| + if (!success)
|
| + delete this;
|
| return success;
|
| }
|
|
|
| @@ -57,9 +64,12 @@ void Session::Terminate() {
|
| RunSessionTask(NewRunnableMethod(
|
| this,
|
| &Session::TerminateOnSessionThread));
|
| + delete this;
|
| }
|
|
|
| -ErrorCode Session::ExecuteScript(const std::string& script,
|
| +ErrorCode Session::ExecuteScript(int window_id,
|
| + const std::string& frame_xpath,
|
| + const std::string& script,
|
| const ListValue* const args,
|
| Value** value) {
|
| std::string args_as_json;
|
| @@ -80,15 +90,17 @@ ErrorCode Session::ExecuteScript(const std::string& script,
|
| VLOG(1) << "Executing script in frame: " << current_frame_xpath_;
|
|
|
| std::string result;
|
| - bool success;
|
| + bool success = false;
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::ExecuteScript,
|
| - current_frame_xpath_,
|
| + window_id,
|
| + frame_xpath,
|
| jscript,
|
| &result,
|
| &success));
|
| if (!success) {
|
| + LOG(ERROR) << "Automation failed to execute script";
|
| *value = Value::CreateStringValue(
|
| "Unknown internal script execution failure");
|
| return kUnknownError;
|
| @@ -98,12 +110,14 @@ ErrorCode Session::ExecuteScript(const std::string& script,
|
| scoped_ptr<Value> r(base::JSONReader::ReadAndReturnError(
|
| result, true, NULL, NULL));
|
| if (!r.get()) {
|
| + LOG(ERROR) << "Failed to parse script result";
|
| *value = Value::CreateStringValue(
|
| "Internal script execution error: failed to parse script result");
|
| return kUnknownError;
|
| }
|
|
|
| if (r->GetType() != Value::TYPE_DICTIONARY) {
|
| + LOG(ERROR) << "Execute script returned non-dictionary type";
|
| std::ostringstream stream;
|
| stream << "Internal script execution error: script result must be a "
|
| << print_valuetype(Value::TYPE_DICTIONARY) << ", but was "
|
| @@ -130,6 +144,13 @@ ErrorCode Session::ExecuteScript(const std::string& script,
|
| return static_cast<ErrorCode>(status);
|
| }
|
|
|
| +ErrorCode Session::ExecuteScript(const std::string& script,
|
| + const ListValue* const args,
|
| + Value** value) {
|
| + return ExecuteScript(
|
| + current_window_id_, current_frame_xpath_, script, args, value);
|
| +}
|
| +
|
| ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) {
|
| ListValue args;
|
| args.Append(element);
|
| @@ -138,8 +159,10 @@ ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) {
|
| Value* unscoped_result = NULL;
|
| ErrorCode code = ExecuteScript(script, &args, &unscoped_result);
|
| scoped_ptr<Value> result(unscoped_result);
|
| - if (code != kSuccess)
|
| + if (code != kSuccess) {
|
| + LOG(ERROR) << "Failed to focus element before sending keys";
|
| return code;
|
| + }
|
|
|
| bool success = false;
|
| RunSessionTask(NewRunnableMethod(
|
| @@ -157,6 +180,7 @@ bool Session::NavigateToURL(const std::string& url) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::NavigateToURL,
|
| + current_window_id_,
|
| url,
|
| &success));
|
| return success;
|
| @@ -167,6 +191,7 @@ bool Session::GoForward() {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GoForward,
|
| + current_window_id_,
|
| &success));
|
| return success;
|
| }
|
| @@ -176,6 +201,7 @@ bool Session::GoBack() {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GoBack,
|
| + current_window_id_,
|
| &success));
|
| return success;
|
| }
|
| @@ -185,6 +211,7 @@ bool Session::Reload() {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::Reload,
|
| + current_window_id_,
|
| &success));
|
| return success;
|
| }
|
| @@ -194,6 +221,7 @@ bool Session::GetURL(std::string* url) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GetURL,
|
| + current_window_id_,
|
| url,
|
| &success));
|
| return success;
|
| @@ -204,6 +232,7 @@ bool Session::GetURL(GURL* gurl) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GetGURL,
|
| + current_window_id_,
|
| gurl,
|
| &success));
|
| return success;
|
| @@ -214,6 +243,7 @@ bool Session::GetTabTitle(std::string* tab_title) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GetTabTitle,
|
| + current_window_id_,
|
| tab_title,
|
| &success));
|
| return success;
|
| @@ -224,6 +254,7 @@ bool Session::GetCookies(const GURL& url, std::string* cookies) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GetCookies,
|
| + current_window_id_,
|
| url,
|
| cookies,
|
| &success));
|
| @@ -237,6 +268,7 @@ bool Session::GetCookieByName(const GURL& url,
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::GetCookieByName,
|
| + current_window_id_,
|
| url,
|
| cookie_name,
|
| cookie,
|
| @@ -249,6 +281,7 @@ bool Session::DeleteCookie(const GURL& url, const std::string& cookie_name) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::DeleteCookie,
|
| + current_window_id_,
|
| url,
|
| cookie_name,
|
| &success));
|
| @@ -260,12 +293,131 @@ bool Session::SetCookie(const GURL& url, const std::string& cookie) {
|
| RunSessionTask(NewRunnableMethod(
|
| automation_.get(),
|
| &Automation::SetCookie,
|
| + current_window_id_,
|
| url,
|
| cookie,
|
| &success));
|
| return success;
|
| }
|
|
|
| +bool Session::GetWindowIds(std::vector<int>* window_ids) {
|
| + bool success = false;
|
| + RunSessionTask(NewRunnableMethod(
|
| + automation_.get(),
|
| + &Automation::GetTabIds,
|
| + window_ids,
|
| + &success));
|
| + return success;
|
| +}
|
| +
|
| +ErrorCode Session::SwitchToWindow(const std::string& name) {
|
| + int switch_to_id = 0;
|
| + int name_no = 0;
|
| + if (base::StringToInt(name, &name_no)) {
|
| + bool does_exist = false;
|
| + RunSessionTask(NewRunnableMethod(
|
| + automation_.get(),
|
| + &Automation::DoesTabExist,
|
| + name_no,
|
| + &does_exist));
|
| + if (does_exist)
|
| + switch_to_id = name_no;
|
| + }
|
| +
|
| + if (!switch_to_id) {
|
| + std::vector<int> window_ids;
|
| + GetWindowIds(&window_ids);
|
| + // See if any of the window names match |name|.
|
| + for (size_t i = 0; i < window_ids.size(); ++i) {
|
| + ListValue empty_list;
|
| + Value* unscoped_name_value;
|
| + std::string window_name;
|
| + ErrorCode code = ExecuteScript(window_ids[i],
|
| + "",
|
| + "return window.name;",
|
| + &empty_list,
|
| + &unscoped_name_value);
|
| + scoped_ptr<Value> name_value(unscoped_name_value);
|
| + if (code == kSuccess &&
|
| + name_value->GetAsString(&window_name) &&
|
| + name == window_name) {
|
| + switch_to_id = window_ids[i];
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (!switch_to_id)
|
| + return kNoSuchWindow;
|
| + current_window_id_ = switch_to_id;
|
| + current_frame_xpath_ = "";
|
| + return kSuccess;
|
| +}
|
| +
|
| +ErrorCode Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) {
|
| + std::string script =
|
| + "var arg = arguments[0];"
|
| + "var xpath = '(/html/body//iframe|/html/frameset/frame)';"
|
| + "var sub = function(s) { return s.replace(/\\$/g, arg); };"
|
| + "xpath += sub('[@name=\"$\" or @id=\"$\"]');"
|
| + "var frame = document.evaluate(xpath, document, null, "
|
| + " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
|
| + "if (!frame) { return null; }"
|
| + "xpath = frame.tagName == 'IFRAME' ? '/html/body//iframe'"
|
| + " : '/html/frameset/frame';"
|
| + "return xpath + sub('[@' + (frame.id == arg ? 'id' : 'name')"
|
| + " + '=\"$\"]');";
|
| + ListValue args;
|
| + args.Append(new StringValue(name_or_id));
|
| + return SwitchToFrameWithJavaScriptLocatedFrame(script, &args);
|
| +}
|
| +
|
| +ErrorCode Session::SwitchToFrameWithIndex(int index) {
|
| + // We cannot simply index into window.frames because we need to know the
|
| + // tagName of the frameElement. If child frame N is from another domain, then
|
| + // the following will run afoul of the same origin policy:
|
| + // window.frames[N].frameElement;
|
| + // Instead of indexing window.frames, we use a an XPath expression to index
|
| + // into the list of all IFRAME and FRAME elements on the page - if we find
|
| + // something, then that XPath expression can be used as the new frame's XPath.
|
| + std::string script =
|
| + "var index = '[' + (arguments[0] + 1) + ']';"
|
| + "var xpath = '(/html/body//iframe|/html/frameset/frame)' + "
|
| + " index;"
|
| + "console.info('searching for frame by xpath: ' + xpath);"
|
| + "var frame = document.evaluate(xpath, document, null, "
|
| + "XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
|
| + "console.info(frame == null ? 'found nothing' : frame);"
|
| + "return frame == null ? null : ((frame.tagName == 'IFRAME' ? "
|
| + " '/html/body//iframe' : '/html/frameset/frame') + index);";
|
| + ListValue args;
|
| + args.Append(Value::CreateIntegerValue(index));
|
| + return SwitchToFrameWithJavaScriptLocatedFrame(script, &args);
|
| +}
|
| +
|
| +bool Session::CloseWindow() {
|
| + bool success = false;
|
| + RunSessionTask(NewRunnableMethod(
|
| + automation_.get(),
|
| + &Automation::CloseTab,
|
| + current_window_id_,
|
| + &success));
|
| +
|
| + if (success) {
|
| + std::vector<int> window_ids;
|
| + if (!GetWindowIds(&window_ids) || window_ids.empty()) {
|
| + // The automation connection will soon be closed, if not already,
|
| + // because we supposedly just closed the last window. Terminate the
|
| + // session.
|
| + // TODO(kkania): This will cause us problems if GetWindowIds fails for a
|
| + // reason other than the channel is disconnected. Look into having
|
| + // |GetWindowIds| tell us if it just closed the last window.
|
| + Terminate();
|
| + }
|
| + }
|
| + return success;
|
| +}
|
| +
|
| void Session::RunSessionTask(Task* task) {
|
| base::WaitableEvent done_event(false, false);
|
| thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(
|
| @@ -286,10 +438,26 @@ void Session::RunSessionTaskOnSessionThread(Task* task,
|
| void Session::InitOnSessionThread(bool* success) {
|
| automation_.reset(new Automation());
|
| automation_->Init(success);
|
| + if (!*success)
|
| + return;
|
| +
|
| + std::vector<int> tab_ids;
|
| + automation_->GetTabIds(&tab_ids, success);
|
| + if (!*success) {
|
| + LOG(ERROR) << "Could not get tab ids";
|
| + return;
|
| + }
|
| + if (tab_ids.empty()) {
|
| + LOG(ERROR) << "No tab ids after initialization";
|
| + *success = false;
|
| + } else {
|
| + current_window_id_ = tab_ids[0];
|
| + }
|
| }
|
|
|
| void Session::TerminateOnSessionThread() {
|
| - automation_->Terminate();
|
| + if (automation_.get())
|
| + automation_->Terminate();
|
| automation_.reset();
|
| }
|
|
|
| @@ -300,7 +468,8 @@ void Session::SendKeysOnSessionThread(const string16& keys,
|
| 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);
|
| + automation_->SendWebKeyEvent(
|
| + current_window_id_, key_events[i], &key_success);
|
| if (!key_success) {
|
| LOG(ERROR) << "Failed to send key event. Event details:\n"
|
| << "Type: " << key_events[i].type << "\n"
|
| @@ -313,4 +482,22 @@ void Session::SendKeysOnSessionThread(const string16& keys,
|
| }
|
| }
|
|
|
| +ErrorCode Session::SwitchToFrameWithJavaScriptLocatedFrame(
|
| + const std::string& script,
|
| + ListValue* args) {
|
| + Value* unscoped_result = NULL;
|
| + ErrorCode code = ExecuteScript(script, args, &unscoped_result);
|
| + scoped_ptr<Value> result(unscoped_result);
|
| + if (code != kSuccess)
|
| + return code;
|
| + std::string xpath;
|
| + if (result->GetAsString(&xpath)) {
|
| + if (current_frame_xpath_.length())
|
| + current_frame_xpath_ += "\n";
|
| + current_frame_xpath_ += xpath;
|
| + return kSuccess;
|
| + }
|
| + return kNoSuchFrame;
|
| +}
|
| +
|
| } // namespace webdriver
|
|
|