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