1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/test/chromedriver/element_commands.h" 6 7 #include <cmath> 8 #include <list> 9 #include <vector> 10 11 #include "base/callback.h" 12 #include "base/files/file_path.h" 13 #include "base/strings/string_split.h" 14 #include "base/strings/stringprintf.h" 15 #include "base/threading/platform_thread.h" 16 #include "base/time/time.h" 17 #include "base/values.h" 18 #include "chrome/test/chromedriver/basic_types.h" 19 #include "chrome/test/chromedriver/chrome/browser_info.h" 20 #include "chrome/test/chromedriver/chrome/chrome.h" 21 #include "chrome/test/chromedriver/chrome/js.h" 22 #include "chrome/test/chromedriver/chrome/status.h" 23 #include "chrome/test/chromedriver/chrome/ui_events.h" 24 #include "chrome/test/chromedriver/chrome/web_view.h" 25 #include "chrome/test/chromedriver/element_util.h" 26 #include "chrome/test/chromedriver/session.h" 27 #include "chrome/test/chromedriver/util.h" 28 #include "third_party/webdriver/atoms.h" 29 30 const int kFlickTouchEventsPerSecond = 30; 31 32 namespace { 33 34 Status SendKeysToElement( 35 Session* session, 36 WebView* web_view, 37 const std::string& element_id, 38 const base::ListValue* key_list) { 39 bool is_displayed = false; 40 bool is_focused = false; 41 base::TimeTicks start_time = base::TimeTicks::Now(); 42 while (true) { 43 Status status = IsElementDisplayed( 44 session, web_view, element_id, true, &is_displayed); 45 if (status.IsError()) 46 return status; 47 if (is_displayed) 48 break; 49 status = IsElementFocused(session, web_view, element_id, &is_focused); 50 if (status.IsError()) 51 return status; 52 if (is_focused) 53 break; 54 if (base::TimeTicks::Now() - start_time >= session->implicit_wait) { 55 return Status(kElementNotVisible); 56 } 57 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); 58 } 59 60 bool is_enabled = false; 61 Status status = IsElementEnabled(session, web_view, element_id, &is_enabled); 62 if (status.IsError()) 63 return status; 64 if (!is_enabled) 65 return Status(kInvalidElementState); 66 67 if (!is_focused) { 68 base::ListValue args; 69 args.Append(CreateElement(element_id)); 70 scoped_ptr<base::Value> result; 71 status = web_view->CallFunction( 72 session->GetCurrentFrameId(), kFocusScript, args, &result); 73 if (status.IsError()) 74 return status; 75 } 76 77 return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers); 78 } 79 80 Status ExecuteTouchSingleTapAtom( 81 Session* session, 82 WebView* web_view, 83 const std::string& element_id, 84 const base::DictionaryValue& params, 85 scoped_ptr<base::Value>* value) { 86 base::ListValue args; 87 args.Append(CreateElement(element_id)); 88 return web_view->CallFunction( 89 session->GetCurrentFrameId(), 90 webdriver::atoms::asString(webdriver::atoms::TOUCH_SINGLE_TAP), 91 args, 92 value); 93 } 94 95 } // namespace 96 97 Status ExecuteElementCommand( 98 const ElementCommand& command, 99 Session* session, 100 WebView* web_view, 101 const base::DictionaryValue& params, 102 scoped_ptr<base::Value>* value) { 103 std::string id; 104 if (params.GetString("id", &id) || params.GetString("element", &id)) 105 return command.Run(session, web_view, id, params, value); 106 return Status(kUnknownError, "element identifier must be a string"); 107 } 108 109 Status ExecuteFindChildElement( 110 int interval_ms, 111 Session* session, 112 WebView* web_view, 113 const std::string& element_id, 114 const base::DictionaryValue& params, 115 scoped_ptr<base::Value>* value) { 116 return FindElement( 117 interval_ms, true, &element_id, session, web_view, params, value); 118 } 119 120 Status ExecuteFindChildElements( 121 int interval_ms, 122 Session* session, 123 WebView* web_view, 124 const std::string& element_id, 125 const base::DictionaryValue& params, 126 scoped_ptr<base::Value>* value) { 127 return FindElement( 128 interval_ms, false, &element_id, session, web_view, params, value); 129 } 130 131 Status ExecuteHoverOverElement( 132 Session* session, 133 WebView* web_view, 134 const std::string& element_id, 135 const base::DictionaryValue& params, 136 scoped_ptr<base::Value>* value) { 137 WebPoint location; 138 Status status = GetElementClickableLocation( 139 session, web_view, element_id, &location); 140 if (status.IsError()) 141 return status; 142 143 MouseEvent move_event( 144 kMovedMouseEventType, kNoneMouseButton, location.x, location.y, 145 session->sticky_modifiers, 0); 146 std::list<MouseEvent> events; 147 events.push_back(move_event); 148 status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); 149 if (status.IsOk()) 150 session->mouse_position = location; 151 return status; 152 } 153 154 Status ExecuteClickElement( 155 Session* session, 156 WebView* web_view, 157 const std::string& element_id, 158 const base::DictionaryValue& params, 159 scoped_ptr<base::Value>* value) { 160 std::string tag_name; 161 Status status = GetElementTagName(session, web_view, element_id, &tag_name); 162 if (status.IsError()) 163 return status; 164 if (tag_name == "option") { 165 bool is_toggleable; 166 status = IsOptionElementTogglable( 167 session, web_view, element_id, &is_toggleable); 168 if (status.IsError()) 169 return status; 170 if (is_toggleable) 171 return ToggleOptionElement(session, web_view, element_id); 172 else 173 return SetOptionElementSelected(session, web_view, element_id, true); 174 } else { 175 WebPoint location; 176 status = GetElementClickableLocation( 177 session, web_view, element_id, &location); 178 if (status.IsError()) 179 return status; 180 181 std::list<MouseEvent> events; 182 events.push_back( 183 MouseEvent(kMovedMouseEventType, kNoneMouseButton, 184 location.x, location.y, session->sticky_modifiers, 0)); 185 events.push_back( 186 MouseEvent(kPressedMouseEventType, kLeftMouseButton, 187 location.x, location.y, session->sticky_modifiers, 1)); 188 events.push_back( 189 MouseEvent(kReleasedMouseEventType, kLeftMouseButton, 190 location.x, location.y, session->sticky_modifiers, 1)); 191 status = 192 web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); 193 if (status.IsOk()) 194 session->mouse_position = location; 195 return status; 196 } 197 } 198 199 Status ExecuteTouchSingleTap( 200 Session* session, 201 WebView* web_view, 202 const std::string& element_id, 203 const base::DictionaryValue& params, 204 scoped_ptr<base::Value>* value) { 205 // Fall back to javascript atom for pre-m30 Chrome. 206 if (session->chrome->GetBrowserInfo()->build_no < 1576) 207 return ExecuteTouchSingleTapAtom( 208 session, web_view, element_id, params, value); 209 210 WebPoint location; 211 Status status = GetElementClickableLocation( 212 session, web_view, element_id, &location); 213 if (status.IsError()) 214 return status; 215 216 std::list<TouchEvent> events; 217 events.push_back( 218 TouchEvent(kTouchStart, location.x, location.y)); 219 events.push_back( 220 TouchEvent(kTouchEnd, location.x, location.y)); 221 return web_view->DispatchTouchEvents(events); 222 } 223 224 Status ExecuteFlick( 225 Session* session, 226 WebView* web_view, 227 const std::string& element_id, 228 const base::DictionaryValue& params, 229 scoped_ptr<base::Value>* value) { 230 WebPoint location; 231 Status status = GetElementClickableLocation( 232 session, web_view, element_id, &location); 233 if (status.IsError()) 234 return status; 235 236 int xoffset, yoffset, speed; 237 if (!params.GetInteger("xoffset", &xoffset)) 238 return Status(kUnknownError, "'xoffset' must be an integer"); 239 if (!params.GetInteger("yoffset", &yoffset)) 240 return Status(kUnknownError, "'yoffset' must be an integer"); 241 if (!params.GetInteger("speed", &speed)) 242 return Status(kUnknownError, "'speed' must be an integer"); 243 if (speed < 1) 244 return Status(kUnknownError, "'speed' must be a positive integer"); 245 246 status = web_view->DispatchTouchEvent( 247 TouchEvent(kTouchStart, location.x, location.y)); 248 if (status.IsError()) 249 return status; 250 251 const double offset = 252 std::sqrt(static_cast<double>(xoffset * xoffset + yoffset * yoffset)); 253 const double xoffset_per_event = 254 (speed * xoffset) / (kFlickTouchEventsPerSecond * offset); 255 const double yoffset_per_event = 256 (speed * yoffset) / (kFlickTouchEventsPerSecond * offset); 257 const int total_events = 258 (offset * kFlickTouchEventsPerSecond) / speed; 259 for (int i = 0; i < total_events; i++) { 260 status = web_view->DispatchTouchEvent( 261 TouchEvent(kTouchMove, 262 location.x + xoffset_per_event * i, 263 location.y + yoffset_per_event * i)); 264 if (status.IsError()) 265 return status; 266 base::PlatformThread::Sleep( 267 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond)); 268 } 269 return web_view->DispatchTouchEvent( 270 TouchEvent(kTouchEnd, location.x + xoffset, location.y + yoffset)); 271 } 272 273 Status ExecuteClearElement( 274 Session* session, 275 WebView* web_view, 276 const std::string& element_id, 277 const base::DictionaryValue& params, 278 scoped_ptr<base::Value>* value) { 279 base::ListValue args; 280 args.Append(CreateElement(element_id)); 281 scoped_ptr<base::Value> result; 282 return web_view->CallFunction( 283 session->GetCurrentFrameId(), 284 webdriver::atoms::asString(webdriver::atoms::CLEAR), 285 args, &result); 286 } 287 288 Status ExecuteSendKeysToElement( 289 Session* session, 290 WebView* web_view, 291 const std::string& element_id, 292 const base::DictionaryValue& params, 293 scoped_ptr<base::Value>* value) { 294 const base::ListValue* key_list; 295 if (!params.GetList("value", &key_list)) 296 return Status(kUnknownError, "'value' must be a list"); 297 298 bool is_input = false; 299 Status status = IsElementAttributeEqualToIgnoreCase( 300 session, web_view, element_id, "tagName", "input", &is_input); 301 if (status.IsError()) 302 return status; 303 bool is_file = false; 304 status = IsElementAttributeEqualToIgnoreCase( 305 session, web_view, element_id, "type", "file", &is_file); 306 if (status.IsError()) 307 return status; 308 if (is_input && is_file) { 309 // Compress array into a single string. 310 base::FilePath::StringType paths_string; 311 for (size_t i = 0; i < key_list->GetSize(); ++i) { 312 base::FilePath::StringType path_part; 313 if (!key_list->GetString(i, &path_part)) 314 return Status(kUnknownError, "'value' is invalid"); 315 paths_string.append(path_part); 316 } 317 318 // Separate the string into separate paths, delimited by '\n'. 319 std::vector<base::FilePath::StringType> path_strings; 320 base::SplitString(paths_string, '\n', &path_strings); 321 std::vector<base::FilePath> paths; 322 for (size_t i = 0; i < path_strings.size(); ++i) 323 paths.push_back(base::FilePath(path_strings[i])); 324 325 bool multiple = false; 326 status = IsElementAttributeEqualToIgnoreCase( 327 session, web_view, element_id, "multiple", "true", &multiple); 328 if (status.IsError()) 329 return status; 330 if (!multiple && paths.size() > 1) 331 return Status(kUnknownError, "the element can not hold multiple files"); 332 333 scoped_ptr<base::DictionaryValue> element(CreateElement(element_id)); 334 return web_view->SetFileInputFiles( 335 session->GetCurrentFrameId(), *element, paths); 336 } else { 337 return SendKeysToElement(session, web_view, element_id, key_list); 338 } 339 } 340 341 Status ExecuteSubmitElement( 342 Session* session, 343 WebView* web_view, 344 const std::string& element_id, 345 const base::DictionaryValue& params, 346 scoped_ptr<base::Value>* value) { 347 base::ListValue args; 348 args.Append(CreateElement(element_id)); 349 return web_view->CallFunction( 350 session->GetCurrentFrameId(), 351 webdriver::atoms::asString(webdriver::atoms::SUBMIT), 352 args, 353 value); 354 } 355 356 Status ExecuteGetElementText( 357 Session* session, 358 WebView* web_view, 359 const std::string& element_id, 360 const base::DictionaryValue& params, 361 scoped_ptr<base::Value>* value) { 362 base::ListValue args; 363 args.Append(CreateElement(element_id)); 364 return web_view->CallFunction( 365 session->GetCurrentFrameId(), 366 webdriver::atoms::asString(webdriver::atoms::GET_TEXT), 367 args, 368 value); 369 } 370 371 Status ExecuteGetElementValue( 372 Session* session, 373 WebView* web_view, 374 const std::string& element_id, 375 const base::DictionaryValue& params, 376 scoped_ptr<base::Value>* value) { 377 base::ListValue args; 378 args.Append(CreateElement(element_id)); 379 return web_view->CallFunction( 380 session->GetCurrentFrameId(), 381 "function(elem) { return elem['value'] }", 382 args, 383 value); 384 } 385 386 Status ExecuteGetElementTagName( 387 Session* session, 388 WebView* web_view, 389 const std::string& element_id, 390 const base::DictionaryValue& params, 391 scoped_ptr<base::Value>* value) { 392 base::ListValue args; 393 args.Append(CreateElement(element_id)); 394 return web_view->CallFunction( 395 session->GetCurrentFrameId(), 396 "function(elem) { return elem.tagName.toLowerCase() }", 397 args, 398 value); 399 } 400 401 Status ExecuteIsElementSelected( 402 Session* session, 403 WebView* web_view, 404 const std::string& element_id, 405 const base::DictionaryValue& params, 406 scoped_ptr<base::Value>* value) { 407 base::ListValue args; 408 args.Append(CreateElement(element_id)); 409 return web_view->CallFunction( 410 session->GetCurrentFrameId(), 411 webdriver::atoms::asString(webdriver::atoms::IS_SELECTED), 412 args, 413 value); 414 } 415 416 Status ExecuteIsElementEnabled( 417 Session* session, 418 WebView* web_view, 419 const std::string& element_id, 420 const base::DictionaryValue& params, 421 scoped_ptr<base::Value>* value) { 422 base::ListValue args; 423 args.Append(CreateElement(element_id)); 424 return web_view->CallFunction( 425 session->GetCurrentFrameId(), 426 webdriver::atoms::asString(webdriver::atoms::IS_ENABLED), 427 args, 428 value); 429 } 430 431 Status ExecuteIsElementDisplayed( 432 Session* session, 433 WebView* web_view, 434 const std::string& element_id, 435 const base::DictionaryValue& params, 436 scoped_ptr<base::Value>* value) { 437 base::ListValue args; 438 args.Append(CreateElement(element_id)); 439 return web_view->CallFunction( 440 session->GetCurrentFrameId(), 441 webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED), 442 args, 443 value); 444 } 445 446 Status ExecuteGetElementLocation( 447 Session* session, 448 WebView* web_view, 449 const std::string& element_id, 450 const base::DictionaryValue& params, 451 scoped_ptr<base::Value>* value) { 452 base::ListValue args; 453 args.Append(CreateElement(element_id)); 454 return web_view->CallFunction( 455 session->GetCurrentFrameId(), 456 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION), 457 args, 458 value); 459 } 460 461 Status ExecuteGetElementLocationOnceScrolledIntoView( 462 Session* session, 463 WebView* web_view, 464 const std::string& element_id, 465 const base::DictionaryValue& params, 466 scoped_ptr<base::Value>* value) { 467 WebPoint location; 468 Status status = ScrollElementIntoView( 469 session, web_view, element_id, &location); 470 if (status.IsError()) 471 return status; 472 value->reset(CreateValueFrom(location)); 473 return Status(kOk); 474 } 475 476 Status ExecuteGetElementSize( 477 Session* session, 478 WebView* web_view, 479 const std::string& element_id, 480 const base::DictionaryValue& params, 481 scoped_ptr<base::Value>* value) { 482 base::ListValue args; 483 args.Append(CreateElement(element_id)); 484 return web_view->CallFunction( 485 session->GetCurrentFrameId(), 486 webdriver::atoms::asString(webdriver::atoms::GET_SIZE), 487 args, 488 value); 489 } 490 491 Status ExecuteGetElementAttribute( 492 Session* session, 493 WebView* web_view, 494 const std::string& element_id, 495 const base::DictionaryValue& params, 496 scoped_ptr<base::Value>* value) { 497 std::string name; 498 if (!params.GetString("name", &name)) 499 return Status(kUnknownError, "missing 'name'"); 500 return GetElementAttribute(session, web_view, element_id, name, value); 501 } 502 503 Status ExecuteGetElementValueOfCSSProperty( 504 Session* session, 505 WebView* web_view, 506 const std::string& element_id, 507 const base::DictionaryValue& params, 508 scoped_ptr<base::Value>* value) { 509 std::string property_name; 510 if (!params.GetString("propertyName", &property_name)) 511 return Status(kUnknownError, "missing 'propertyName'"); 512 std::string property_value; 513 Status status = GetElementEffectiveStyle( 514 session, web_view, element_id, property_name, &property_value); 515 if (status.IsError()) 516 return status; 517 value->reset(new base::StringValue(property_value)); 518 return Status(kOk); 519 } 520 521 Status ExecuteElementEquals( 522 Session* session, 523 WebView* web_view, 524 const std::string& element_id, 525 const base::DictionaryValue& params, 526 scoped_ptr<base::Value>* value) { 527 std::string other_element_id; 528 if (!params.GetString("other", &other_element_id)) 529 return Status(kUnknownError, "'other' must be a string"); 530 value->reset(new base::FundamentalValue(element_id == other_element_id)); 531 return Status(kOk); 532 } 533