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; |
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 std::vector<int> window_ids; | |
246 if (!GetWindowIds(&window_ids)) { | |
247 LOG(ERROR) << "Could not get window handles"; | |
248 return kUnknownError; | |
249 } | |
250 int switch_to_id = 0; | |
251 int name_no = 0; | |
252 if (base::StringToInt(name, &name_no)) { | |
253 for (size_t i = 0; i < window_ids.size(); ++i) { | |
Paweł Hajdan Jr.
2011/02/14 09:29:43
Another case of linear-scan lookup. Can we make it
kkania
2011/02/14 17:42:12
I can add a method to Automation which does someth
| |
254 if (window_ids[i] == name_no) { | |
255 switch_to_id = name_no; | |
256 break; | |
257 } | |
258 } | |
259 } | |
260 | |
261 if (!switch_to_id) { | |
262 // See if any of the window names match |name|. | |
263 for (size_t i = 0; i < window_ids.size(); ++i) { | |
264 ListValue empty_list; | |
265 Value* unscoped_name_value; | |
266 std::string window_name; | |
267 ErrorCode code = ExecuteScript(window_ids[i], | |
268 "", | |
269 "return window.name;", | |
270 &empty_list, | |
271 &unscoped_name_value); | |
272 scoped_ptr<Value> name_value(unscoped_name_value); | |
273 if (code == kSuccess && | |
274 name_value->GetAsString(&window_name) && | |
275 name == window_name) { | |
276 switch_to_id = window_ids[i]; | |
277 break; | |
278 } | |
279 } | |
280 } | |
281 | |
282 if (!switch_to_id) | |
283 return kNoSuchWindow; | |
284 current_window_id_ = switch_to_id; | |
285 current_frame_xpath_ = ""; | |
286 return kSuccess; | |
287 } | |
288 | |
289 ErrorCode Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) { | |
290 std::string script = | |
291 "var arg = arguments[0];" | |
292 "var xpath = '(/html/body//iframe|/html/frameset/frame)';" | |
293 "var sub = function(s) { return s.replace(/\\$/g, arg); };" | |
294 "xpath += sub('[@name=\"$\" or @id=\"$\"]');" | |
295 "var frame = document.evaluate(xpath, document, null, " | |
296 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" | |
297 "if (!frame) { return null; }" | |
298 "xpath = frame.tagName == 'IFRAME' ? '/html/body//iframe'" | |
299 " : '/html/frameset/frame';" | |
300 "return xpath + sub('[@' + (frame.id == arg ? 'id' : 'name')" | |
301 " + '=\"$\"]');"; | |
302 ListValue args; | |
303 args.Append(new StringValue(name_or_id)); | |
304 return SwitchToFrameWithJavaScriptLocatedFrame(script, &args); | |
305 } | |
306 | |
307 ErrorCode Session::SwitchToFrameWithIndex(int index) { | |
308 // We cannot simply index into window.frames because we need to know the | |
309 // tagName of the frameElement. If child frame N is from another domain, then | |
310 // the following will run afoul of the same origin policy: | |
311 // window.frames[N].frameElement; | |
312 // Instead of indexing window.frames, we use a an XPath expression to index | |
313 // into the list of all IFRAME and FRAME elements on the page - if we find | |
314 // something, then that XPath expression can be used as the new frame's XPath. | |
315 std::string script = | |
316 "var index = '[' + (arguments[0] + 1) + ']';" | |
317 "var xpath = '(/html/body//iframe|/html/frameset/frame)' + " | |
318 " index;" | |
319 "console.info('searching for frame by xpath: ' + xpath);" | |
320 "var frame = document.evaluate(xpath, document, null, " | |
321 "XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" | |
322 "console.info(frame == null ? 'found nothing' : frame);" | |
323 "return frame == null ? null : ((frame.tagName == 'IFRAME' ? " | |
324 " '/html/body//iframe' : '/html/frameset/frame') + index);"; | |
325 ListValue args; | |
326 args.Append(Value::CreateIntegerValue(index)); | |
327 return SwitchToFrameWithJavaScriptLocatedFrame(script, &args); | |
328 } | |
329 | |
330 bool Session::CloseWindow() { | |
331 bool success = false; | |
332 | |
333 std::vector<int> window_ids; | |
334 if (!GetWindowIds(&window_ids) || window_ids.empty()) { | |
335 LOG(ERROR) << "Could not get window ids"; | |
336 return false; | |
337 } | |
338 RunSessionTask(NewRunnableMethod( | |
339 automation_.get(), | |
340 &Automation::CloseTab, | |
341 current_window_id_, | |
342 &success)); | |
343 if (success) { | |
344 // The automation connection will have closed if we just closed the last | |
345 // window, so terminate the session. | |
346 if (window_ids.size() == 1u) | |
347 Terminate(); | |
348 } | |
349 return success; | |
350 } | |
351 | |
210 void Session::RunSessionTask(Task* task) { | 352 void Session::RunSessionTask(Task* task) { |
211 base::WaitableEvent done_event(false, false); | 353 base::WaitableEvent done_event(false, false); |
212 thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod( | 354 thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod( |
213 this, | 355 this, |
214 &Session::RunSessionTaskOnSessionThread, | 356 &Session::RunSessionTaskOnSessionThread, |
215 task, | 357 task, |
216 &done_event)); | 358 &done_event)); |
217 done_event.Wait(); | 359 done_event.Wait(); |
218 } | 360 } |
219 | 361 |
220 void Session::RunSessionTaskOnSessionThread(Task* task, | 362 void Session::RunSessionTaskOnSessionThread(Task* task, |
221 base::WaitableEvent* done_event) { | 363 base::WaitableEvent* done_event) { |
222 task->Run(); | 364 task->Run(); |
223 delete task; | 365 delete task; |
224 done_event->Signal(); | 366 done_event->Signal(); |
225 } | 367 } |
226 | 368 |
227 void Session::InitOnSessionThread(bool* success) { | 369 void Session::InitOnSessionThread(bool* success) { |
228 automation_.reset(new Automation()); | 370 automation_.reset(new Automation()); |
229 automation_->Init(success); | 371 automation_->Init(success); |
372 if (!*success) | |
373 return; | |
374 | |
375 std::vector<int> tab_ids; | |
376 automation_->GetTabIds(&tab_ids, success); | |
377 if (!*success) { | |
378 LOG(ERROR) << "Could not get tab ids"; | |
379 return; | |
380 } | |
381 if (tab_ids.empty()) { | |
382 LOG(ERROR) << "No tab ids after initialization"; | |
383 *success = false; | |
384 } else { | |
385 current_window_id_ = tab_ids[0]; | |
386 } | |
230 } | 387 } |
231 | 388 |
232 void Session::TerminateOnSessionThread() { | 389 void Session::TerminateOnSessionThread() { |
233 automation_->Terminate(); | 390 if (automation_.get()) |
391 automation_->Terminate(); | |
234 automation_.reset(); | 392 automation_.reset(); |
235 } | 393 } |
236 | 394 |
237 void Session::SendKeysOnSessionThread(const string16& keys, | 395 void Session::SendKeysOnSessionThread(const string16& keys, |
238 bool* success) { | 396 bool* success) { |
239 *success = true; | 397 *success = true; |
240 std::vector<WebKeyEvent> key_events; | 398 std::vector<WebKeyEvent> key_events; |
241 ConvertKeysToWebKeyEvents(keys, &key_events); | 399 ConvertKeysToWebKeyEvents(keys, &key_events); |
242 for (size_t i = 0; i < key_events.size(); ++i) { | 400 for (size_t i = 0; i < key_events.size(); ++i) { |
243 bool key_success = false; | 401 bool key_success = false; |
244 automation_->SendWebKeyEvent(key_events[i], &key_success); | 402 automation_->SendWebKeyEvent( |
403 current_window_id_, key_events[i], &key_success); | |
245 if (!key_success) { | 404 if (!key_success) { |
246 LOG(ERROR) << "Failed to send key event. Event details:\n" | 405 LOG(ERROR) << "Failed to send key event. Event details:\n" |
247 << "Type: " << key_events[i].type << "\n" | 406 << "Type: " << key_events[i].type << "\n" |
248 << "KeyCode: " << key_events[i].key_code << "\n" | 407 << "KeyCode: " << key_events[i].key_code << "\n" |
249 << "UnmodifiedText: " << key_events[i].unmodified_text << "\n" | 408 << "UnmodifiedText: " << key_events[i].unmodified_text << "\n" |
250 << "ModifiedText: " << key_events[i].modified_text << "\n" | 409 << "ModifiedText: " << key_events[i].modified_text << "\n" |
251 << "Modifiers: " << key_events[i].modifiers << "\n"; | 410 << "Modifiers: " << key_events[i].modifiers << "\n"; |
252 *success = false; | 411 *success = false; |
253 } | 412 } |
254 } | 413 } |
255 } | 414 } |
256 | 415 |
416 ErrorCode Session::SwitchToFrameWithJavaScriptLocatedFrame( | |
417 const std::string& script, | |
418 ListValue* args) { | |
419 Value* unscoped_result = NULL; | |
420 ErrorCode code = ExecuteScript(script, args, &unscoped_result); | |
421 scoped_ptr<Value> result(unscoped_result); | |
422 if (code != kSuccess) | |
423 return code; | |
424 std::string xpath; | |
425 if (result->GetAsString(&xpath)) { | |
426 if (current_frame_xpath_.length()) | |
427 current_frame_xpath_ += "\n"; | |
428 current_frame_xpath_ += xpath; | |
429 return kSuccess; | |
430 } | |
431 return kNoSuchFrame; | |
432 } | |
433 | |
257 } // namespace webdriver | 434 } // namespace webdriver |
OLD | NEW |