OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/test/webdriver/session_manager.h" | 5 #include "chrome/test/webdriver/session_manager.h" |
6 | 6 |
7 #include <vector> | 7 #include <vector> |
8 | 8 |
9 #include "base/command_line.h" | 9 #include "base/command_line.h" |
10 #include "base/file_util.h" | 10 #include "base/file_util.h" |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/message_loop_proxy.h" | 12 #include "base/message_loop_proxy.h" |
13 #include "base/process.h" | 13 #include "base/process.h" |
14 #include "base/process_util.h" | 14 #include "base/process_util.h" |
| 15 #include "base/scoped_ptr.h" |
| 16 #include "base/string_number_conversions.h" |
15 #include "base/string_util.h" | 17 #include "base/string_util.h" |
16 #include "base/json/json_reader.h" | 18 #include "base/json/json_reader.h" |
17 #include "base/json/json_writer.h" | 19 #include "base/json/json_writer.h" |
18 #include "base/test/test_timeouts.h" | 20 #include "base/test/test_timeouts.h" |
19 #include "base/utf_string_conversions.h" | 21 #include "base/utf_string_conversions.h" |
20 #include "chrome/app/chrome_command_ids.h" | 22 #include "chrome/app/chrome_command_ids.h" |
21 #include "chrome/common/chrome_constants.h" | 23 #include "chrome/common/chrome_constants.h" |
22 #include "chrome/common/chrome_switches.h" | 24 #include "chrome/common/chrome_switches.h" |
23 #include "chrome/test/test_launcher_utils.h" | 25 #include "chrome/test/test_launcher_utils.h" |
| 26 #include "chrome/test/webdriver/session_manager.h" |
24 #include "chrome/test/webdriver/utility_functions.h" | 27 #include "chrome/test/webdriver/utility_functions.h" |
25 #include "chrome/test/webdriver/webdriver_key_converter.h" | 28 #include "chrome/test/webdriver/webdriver_key_converter.h" |
26 #include "third_party/webdriver/atoms.h" | 29 #include "third_party/webdriver/atoms.h" |
27 | 30 |
28 namespace webdriver { | 31 namespace webdriver { |
29 | 32 |
30 Session::Session(const std::string& id) | 33 Session::Session() |
31 : thread_(id.c_str()), | 34 : id_(GenerateRandomID()), |
32 id_(id), | 35 thread_(id_.c_str()), |
33 window_num_(0), | |
34 implicit_wait_(0), | 36 implicit_wait_(0), |
35 current_frame_xpath_("") { | 37 current_frame_xpath_(""), |
| 38 current_window_id_(0) { |
| 39 SessionManager::GetInstance()->Add(this); |
36 } | 40 } |
37 | 41 |
38 Session::~Session() {} | 42 Session::~Session() { |
| 43 SessionManager::GetInstance()->Remove(id_); |
| 44 } |
39 | 45 |
40 bool Session::Init() { | 46 bool Session::Init() { |
41 if (!thread_.Start()) { | 47 bool success = false; |
| 48 if (thread_.Start()) { |
| 49 RunSessionTask(NewRunnableMethod( |
| 50 this, |
| 51 &Session::InitOnSessionThread, |
| 52 &success)); |
| 53 } else { |
42 LOG(ERROR) << "Cannot start session thread"; | 54 LOG(ERROR) << "Cannot start session thread"; |
43 return false; | |
44 } | 55 } |
45 | 56 if (!success) |
46 bool success = false; | 57 delete this; |
47 RunSessionTask(NewRunnableMethod( | |
48 this, | |
49 &Session::InitOnSessionThread, | |
50 &success)); | |
51 return success; | 58 return success; |
52 } | 59 } |
53 | 60 |
54 void Session::Terminate() { | 61 void Session::Terminate() { |
55 RunSessionTask(NewRunnableMethod( | 62 RunSessionTask(NewRunnableMethod( |
56 this, | 63 this, |
57 &Session::TerminateOnSessionThread)); | 64 &Session::TerminateOnSessionThread)); |
| 65 delete this; |
58 } | 66 } |
59 | 67 |
60 ErrorCode Session::ExecuteScript(const std::string& script, | 68 ErrorCode Session::ExecuteScript(int window_id, |
| 69 const std::string& frame_xpath, |
| 70 const std::string& script, |
61 const ListValue* const args, | 71 const ListValue* const args, |
62 Value** value) { | 72 Value** value) { |
63 std::string args_as_json; | 73 std::string args_as_json; |
64 base::JSONWriter::Write(static_cast<const Value* const>(args), | 74 base::JSONWriter::Write(static_cast<const Value* const>(args), |
65 /*pretty_print=*/false, | 75 /*pretty_print=*/false, |
66 &args_as_json); | 76 &args_as_json); |
67 | 77 |
68 std::string jscript = "window.domAutomationController.send((function(){" + | 78 std::string jscript = "window.domAutomationController.send((function(){" + |
69 // Every injected script is fed through the executeScript atom. This atom | 79 // Every injected script is fed through the executeScript atom. This atom |
70 // will catch any errors that are thrown and convert them to the | 80 // will catch any errors that are thrown and convert them to the |
71 // appropriate JSON structure. | 81 // appropriate JSON structure. |
72 build_atom(EXECUTE_SCRIPT, sizeof EXECUTE_SCRIPT) + | 82 build_atom(EXECUTE_SCRIPT, sizeof EXECUTE_SCRIPT) + |
73 "var result = executeScript(function(){" + script + "}," + | 83 "var result = executeScript(function(){" + script + "}," + |
74 args_as_json + ");return JSON.stringify(result);})());"; | 84 args_as_json + ");return JSON.stringify(result);})());"; |
75 | 85 |
76 // Should we also log the script that's being executed? It could be several KB | 86 // Should we also log the script that's being executed? It could be several KB |
77 // in size and will add lots of noise to the logs. | 87 // in size and will add lots of noise to the logs. |
78 VLOG(1) << "Executing script in frame: " << current_frame_xpath_; | 88 VLOG(1) << "Executing script in frame: " << current_frame_xpath_; |
79 | 89 |
80 std::string result; | 90 std::string result; |
81 bool success; | 91 bool success = false; |
82 RunSessionTask(NewRunnableMethod( | 92 RunSessionTask(NewRunnableMethod( |
83 automation_.get(), | 93 automation_.get(), |
84 &Automation::ExecuteScript, | 94 &Automation::ExecuteScript, |
85 current_frame_xpath_, | 95 window_id, |
| 96 frame_xpath, |
86 jscript, | 97 jscript, |
87 &result, | 98 &result, |
88 &success)); | 99 &success)); |
89 if (!success) { | 100 if (!success) { |
90 *value = Value::CreateStringValue( | 101 *value = Value::CreateStringValue( |
91 "Unknown internal script execution failure"); | 102 "Unknown internal script execution failure"); |
92 return kUnknownError; | 103 return kUnknownError; |
93 } | 104 } |
94 | 105 |
95 VLOG(1) << "...script result: " << result; | 106 VLOG(1) << "...script result: " << result; |
(...skipping 25 matching lines...) Expand all Loading... |
121 *value = Value::CreateNullValue(); | 132 *value = Value::CreateNullValue(); |
122 } | 133 } |
123 | 134 |
124 int status; | 135 int status; |
125 if (!result_dict->GetInteger("status", &status)) { | 136 if (!result_dict->GetInteger("status", &status)) { |
126 NOTREACHED() << "...script did not return a status flag."; | 137 NOTREACHED() << "...script did not return a status flag."; |
127 } | 138 } |
128 return static_cast<ErrorCode>(status); | 139 return static_cast<ErrorCode>(status); |
129 } | 140 } |
130 | 141 |
| 142 ErrorCode Session::ExecuteScript(const std::string& script, |
| 143 const ListValue* const args, |
| 144 Value** value) { |
| 145 return ExecuteScript( |
| 146 current_window_id_, current_frame_xpath_, script, args, value); |
| 147 } |
| 148 |
131 ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) { | 149 ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) { |
132 ListValue args; | 150 ListValue args; |
133 args.Append(element); | 151 args.Append(element); |
134 // TODO(jleyba): Update this to use the correct atom. | 152 // TODO(jleyba): Update this to use the correct atom. |
135 std::string script = "document.activeElement.blur();arguments[0].focus();"; | 153 std::string script = "document.activeElement.blur();arguments[0].focus();"; |
136 Value* unscoped_result = NULL; | 154 Value* unscoped_result = NULL; |
137 ErrorCode code = ExecuteScript(script, &args, &unscoped_result); | 155 ErrorCode code = ExecuteScript(script, &args, &unscoped_result); |
138 scoped_ptr<Value> result(unscoped_result); | 156 scoped_ptr<Value> result(unscoped_result); |
139 if (code != kSuccess) | 157 if (code != kSuccess) |
140 return code; | 158 return code; |
141 | 159 |
142 bool success = false; | 160 bool success = false; |
143 RunSessionTask(NewRunnableMethod( | 161 RunSessionTask(NewRunnableMethod( |
144 this, | 162 this, |
145 &Session::SendKeysOnSessionThread, | 163 &Session::SendKeysOnSessionThread, |
146 keys, | 164 keys, |
147 &success)); | 165 &success)); |
148 if (!success) | 166 if (!success) |
149 return kUnknownError; | 167 return kUnknownError; |
150 return kSuccess; | 168 return kSuccess; |
151 } | 169 } |
152 | 170 |
153 bool Session::NavigateToURL(const std::string& url) { | 171 bool Session::NavigateToURL(const std::string& url) { |
154 bool success = false; | 172 bool success = false; |
155 RunSessionTask(NewRunnableMethod( | 173 RunSessionTask(NewRunnableMethod( |
156 automation_.get(), | 174 automation_.get(), |
157 &Automation::NavigateToURL, | 175 &Automation::NavigateToURL, |
| 176 current_window_id_, |
158 url, | 177 url, |
159 &success)); | 178 &success)); |
160 return success; | 179 return success; |
161 } | 180 } |
162 | 181 |
163 bool Session::GoForward() { | 182 bool Session::GoForward() { |
164 bool success = false; | 183 bool success = false; |
165 RunSessionTask(NewRunnableMethod( | 184 RunSessionTask(NewRunnableMethod( |
166 automation_.get(), | 185 automation_.get(), |
167 &Automation::GoForward, | 186 &Automation::GoForward, |
| 187 current_window_id_, |
168 &success)); | 188 &success)); |
169 return success; | 189 return success; |
170 } | 190 } |
171 | 191 |
172 bool Session::GoBack() { | 192 bool Session::GoBack() { |
173 bool success = false; | 193 bool success = false; |
174 RunSessionTask(NewRunnableMethod( | 194 RunSessionTask(NewRunnableMethod( |
175 automation_.get(), | 195 automation_.get(), |
176 &Automation::GoBack, | 196 &Automation::GoBack, |
| 197 current_window_id_, |
177 &success)); | 198 &success)); |
178 return success; | 199 return success; |
179 } | 200 } |
180 | 201 |
181 bool Session::Reload() { | 202 bool Session::Reload() { |
182 bool success = false; | 203 bool success = false; |
183 RunSessionTask(NewRunnableMethod( | 204 RunSessionTask(NewRunnableMethod( |
184 automation_.get(), | 205 automation_.get(), |
185 &Automation::Reload, | 206 &Automation::Reload, |
| 207 current_window_id_, |
186 &success)); | 208 &success)); |
187 return success; | 209 return success; |
188 } | 210 } |
189 | 211 |
190 bool Session::GetURL(std::string* url) { | 212 bool Session::GetURL(std::string* url) { |
191 bool success = false; | 213 bool success = false; |
192 RunSessionTask(NewRunnableMethod( | 214 RunSessionTask(NewRunnableMethod( |
193 automation_.get(), | 215 automation_.get(), |
194 &Automation::GetURL, | 216 &Automation::GetURL, |
| 217 current_window_id_, |
195 url, | 218 url, |
196 &success)); | 219 &success)); |
197 return success; | 220 return success; |
198 } | 221 } |
199 | 222 |
200 bool Session::GetTabTitle(std::string* tab_title) { | 223 bool Session::GetTabTitle(std::string* tab_title) { |
201 bool success = false; | 224 bool success = false; |
202 RunSessionTask(NewRunnableMethod( | 225 RunSessionTask(NewRunnableMethod( |
203 automation_.get(), | 226 automation_.get(), |
204 &Automation::GetTabTitle, | 227 &Automation::GetTabTitle, |
| 228 current_window_id_, |
205 tab_title, | 229 tab_title, |
206 &success)); | 230 &success)); |
207 return success; | 231 return success; |
208 } | 232 } |
209 | 233 |
| 234 bool Session::GetWindowIds(std::vector<int>* window_ids) { |
| 235 bool success = false; |
| 236 RunSessionTask(NewRunnableMethod( |
| 237 automation_.get(), |
| 238 &Automation::GetTabIds, |
| 239 window_ids, |
| 240 &success)); |
| 241 return success; |
| 242 } |
| 243 |
| 244 ErrorCode Session::SwitchToWindow(const std::string& name) { |
| 245 int switch_to_id = 0; |
| 246 int name_no = 0; |
| 247 if (base::StringToInt(name, &name_no)) { |
| 248 bool does_exist = false; |
| 249 RunSessionTask(NewRunnableMethod( |
| 250 automation_.get(), |
| 251 &Automation::DoesTabExist, |
| 252 name_no, |
| 253 &does_exist)); |
| 254 if (does_exist) |
| 255 switch_to_id = name_no; |
| 256 } |
| 257 |
| 258 if (!switch_to_id) { |
| 259 std::vector<int> window_ids; |
| 260 GetWindowIds(&window_ids); |
| 261 // See if any of the window names match |name|. |
| 262 for (size_t i = 0; i < window_ids.size(); ++i) { |
| 263 ListValue empty_list; |
| 264 Value* unscoped_name_value; |
| 265 std::string window_name; |
| 266 ErrorCode code = ExecuteScript(window_ids[i], |
| 267 "", |
| 268 "return window.name;", |
| 269 &empty_list, |
| 270 &unscoped_name_value); |
| 271 scoped_ptr<Value> name_value(unscoped_name_value); |
| 272 if (code == kSuccess && |
| 273 name_value->GetAsString(&window_name) && |
| 274 name == window_name) { |
| 275 switch_to_id = window_ids[i]; |
| 276 break; |
| 277 } |
| 278 } |
| 279 } |
| 280 |
| 281 if (!switch_to_id) |
| 282 return kNoSuchWindow; |
| 283 current_window_id_ = switch_to_id; |
| 284 current_frame_xpath_ = ""; |
| 285 return kSuccess; |
| 286 } |
| 287 |
| 288 ErrorCode Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) { |
| 289 std::string script = |
| 290 "var arg = arguments[0];" |
| 291 "var xpath = '(/html/body//iframe|/html/frameset/frame)';" |
| 292 "var sub = function(s) { return s.replace(/\\$/g, arg); };" |
| 293 "xpath += sub('[@name=\"$\" or @id=\"$\"]');" |
| 294 "var frame = document.evaluate(xpath, document, null, " |
| 295 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" |
| 296 "if (!frame) { return null; }" |
| 297 "xpath = frame.tagName == 'IFRAME' ? '/html/body//iframe'" |
| 298 " : '/html/frameset/frame';" |
| 299 "return xpath + sub('[@' + (frame.id == arg ? 'id' : 'name')" |
| 300 " + '=\"$\"]');"; |
| 301 ListValue args; |
| 302 args.Append(new StringValue(name_or_id)); |
| 303 return SwitchToFrameWithJavaScriptLocatedFrame(script, &args); |
| 304 } |
| 305 |
| 306 ErrorCode Session::SwitchToFrameWithIndex(int index) { |
| 307 // We cannot simply index into window.frames because we need to know the |
| 308 // tagName of the frameElement. If child frame N is from another domain, then |
| 309 // the following will run afoul of the same origin policy: |
| 310 // window.frames[N].frameElement; |
| 311 // Instead of indexing window.frames, we use a an XPath expression to index |
| 312 // into the list of all IFRAME and FRAME elements on the page - if we find |
| 313 // something, then that XPath expression can be used as the new frame's XPath. |
| 314 std::string script = |
| 315 "var index = '[' + (arguments[0] + 1) + ']';" |
| 316 "var xpath = '(/html/body//iframe|/html/frameset/frame)' + " |
| 317 " index;" |
| 318 "console.info('searching for frame by xpath: ' + xpath);" |
| 319 "var frame = document.evaluate(xpath, document, null, " |
| 320 "XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" |
| 321 "console.info(frame == null ? 'found nothing' : frame);" |
| 322 "return frame == null ? null : ((frame.tagName == 'IFRAME' ? " |
| 323 " '/html/body//iframe' : '/html/frameset/frame') + index);"; |
| 324 ListValue args; |
| 325 args.Append(Value::CreateIntegerValue(index)); |
| 326 return SwitchToFrameWithJavaScriptLocatedFrame(script, &args); |
| 327 } |
| 328 |
| 329 bool Session::CloseWindow() { |
| 330 bool success = false; |
| 331 |
| 332 std::vector<int> window_ids; |
| 333 if (!GetWindowIds(&window_ids) || window_ids.empty()) { |
| 334 LOG(ERROR) << "Could not get window ids"; |
| 335 return false; |
| 336 } |
| 337 RunSessionTask(NewRunnableMethod( |
| 338 automation_.get(), |
| 339 &Automation::CloseTab, |
| 340 current_window_id_, |
| 341 &success)); |
| 342 if (success) { |
| 343 // The automation connection will have closed if we just closed the last |
| 344 // window, so terminate the session. |
| 345 if (window_ids.size() == 1u) |
| 346 Terminate(); |
| 347 } |
| 348 return success; |
| 349 } |
| 350 |
210 void Session::RunSessionTask(Task* task) { | 351 void Session::RunSessionTask(Task* task) { |
211 base::WaitableEvent done_event(false, false); | 352 base::WaitableEvent done_event(false, false); |
212 thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod( | 353 thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod( |
213 this, | 354 this, |
214 &Session::RunSessionTaskOnSessionThread, | 355 &Session::RunSessionTaskOnSessionThread, |
215 task, | 356 task, |
216 &done_event)); | 357 &done_event)); |
217 done_event.Wait(); | 358 done_event.Wait(); |
218 } | 359 } |
219 | 360 |
220 void Session::RunSessionTaskOnSessionThread(Task* task, | 361 void Session::RunSessionTaskOnSessionThread(Task* task, |
221 base::WaitableEvent* done_event) { | 362 base::WaitableEvent* done_event) { |
222 task->Run(); | 363 task->Run(); |
223 delete task; | 364 delete task; |
224 done_event->Signal(); | 365 done_event->Signal(); |
225 } | 366 } |
226 | 367 |
227 void Session::InitOnSessionThread(bool* success) { | 368 void Session::InitOnSessionThread(bool* success) { |
228 automation_.reset(new Automation()); | 369 automation_.reset(new Automation()); |
229 automation_->Init(success); | 370 automation_->Init(success); |
| 371 if (!*success) |
| 372 return; |
| 373 |
| 374 std::vector<int> tab_ids; |
| 375 automation_->GetTabIds(&tab_ids, success); |
| 376 if (!*success) { |
| 377 LOG(ERROR) << "Could not get tab ids"; |
| 378 return; |
| 379 } |
| 380 if (tab_ids.empty()) { |
| 381 LOG(ERROR) << "No tab ids after initialization"; |
| 382 *success = false; |
| 383 } else { |
| 384 current_window_id_ = tab_ids[0]; |
| 385 } |
230 } | 386 } |
231 | 387 |
232 void Session::TerminateOnSessionThread() { | 388 void Session::TerminateOnSessionThread() { |
233 automation_->Terminate(); | 389 if (automation_.get()) |
| 390 automation_->Terminate(); |
234 automation_.reset(); | 391 automation_.reset(); |
235 } | 392 } |
236 | 393 |
237 void Session::SendKeysOnSessionThread(const string16& keys, | 394 void Session::SendKeysOnSessionThread(const string16& keys, |
238 bool* success) { | 395 bool* success) { |
239 *success = true; | 396 *success = true; |
240 std::vector<WebKeyEvent> key_events; | 397 std::vector<WebKeyEvent> key_events; |
241 ConvertKeysToWebKeyEvents(keys, &key_events); | 398 ConvertKeysToWebKeyEvents(keys, &key_events); |
242 for (size_t i = 0; i < key_events.size(); ++i) { | 399 for (size_t i = 0; i < key_events.size(); ++i) { |
243 bool key_success = false; | 400 bool key_success = false; |
244 automation_->SendWebKeyEvent(key_events[i], &key_success); | 401 automation_->SendWebKeyEvent( |
| 402 current_window_id_, key_events[i], &key_success); |
245 if (!key_success) { | 403 if (!key_success) { |
246 LOG(ERROR) << "Failed to send key event. Event details:\n" | 404 LOG(ERROR) << "Failed to send key event. Event details:\n" |
247 << "Type: " << key_events[i].type << "\n" | 405 << "Type: " << key_events[i].type << "\n" |
248 << "KeyCode: " << key_events[i].key_code << "\n" | 406 << "KeyCode: " << key_events[i].key_code << "\n" |
249 << "UnmodifiedText: " << key_events[i].unmodified_text << "\n" | 407 << "UnmodifiedText: " << key_events[i].unmodified_text << "\n" |
250 << "ModifiedText: " << key_events[i].modified_text << "\n" | 408 << "ModifiedText: " << key_events[i].modified_text << "\n" |
251 << "Modifiers: " << key_events[i].modifiers << "\n"; | 409 << "Modifiers: " << key_events[i].modifiers << "\n"; |
252 *success = false; | 410 *success = false; |
253 } | 411 } |
254 } | 412 } |
255 } | 413 } |
256 | 414 |
| 415 ErrorCode Session::SwitchToFrameWithJavaScriptLocatedFrame( |
| 416 const std::string& script, |
| 417 ListValue* args) { |
| 418 Value* unscoped_result = NULL; |
| 419 ErrorCode code = ExecuteScript(script, args, &unscoped_result); |
| 420 scoped_ptr<Value> result(unscoped_result); |
| 421 if (code != kSuccess) |
| 422 return code; |
| 423 std::string xpath; |
| 424 if (result->GetAsString(&xpath)) { |
| 425 if (current_frame_xpath_.length()) |
| 426 current_frame_xpath_ += "\n"; |
| 427 current_frame_xpath_ += xpath; |
| 428 return kSuccess; |
| 429 } |
| 430 return kNoSuchFrame; |
| 431 } |
| 432 |
257 } // namespace webdriver | 433 } // namespace webdriver |
OLD | NEW |