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 |