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