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.
5 #include "chrome/test/chromedriver/element_commands.h"
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"
30 const int kFlickTouchEventsPerSecond
= 30;
34 Status
SendKeysToElement(
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();
43 Status status
= IsElementDisplayed(
44 session
, web_view
, element_id
, true, &is_displayed
);
49 status
= IsElementFocused(session
, web_view
, element_id
, &is_focused
);
54 if (base::TimeTicks::Now() - start_time
>= session
->implicit_wait
) {
55 return Status(kElementNotVisible
);
57 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
60 bool is_enabled
= false;
61 Status status
= IsElementEnabled(session
, web_view
, element_id
, &is_enabled
);
65 return Status(kInvalidElementState
);
69 args
.Append(CreateElement(element_id
));
70 scoped_ptr
<base::Value
> result
;
71 status
= web_view
->CallFunction(
72 session
->GetCurrentFrameId(), kFocusScript
, args
, &result
);
77 return SendKeysOnWindow(web_view
, key_list
, true, &session
->sticky_modifiers
);
80 Status
ExecuteTouchSingleTapAtom(
83 const std::string
& element_id
,
84 const base::DictionaryValue
& params
,
85 scoped_ptr
<base::Value
>* value
) {
87 args
.Append(CreateElement(element_id
));
88 return web_view
->CallFunction(
89 session
->GetCurrentFrameId(),
90 webdriver::atoms::asString(webdriver::atoms::TOUCH_SINGLE_TAP
),
97 Status
ExecuteElementCommand(
98 const ElementCommand
& command
,
101 const base::DictionaryValue
& params
,
102 scoped_ptr
<base::Value
>* value
) {
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");
109 Status
ExecuteFindChildElement(
113 const std::string
& element_id
,
114 const base::DictionaryValue
& params
,
115 scoped_ptr
<base::Value
>* value
) {
117 interval_ms
, true, &element_id
, session
, web_view
, params
, value
);
120 Status
ExecuteFindChildElements(
124 const std::string
& element_id
,
125 const base::DictionaryValue
& params
,
126 scoped_ptr
<base::Value
>* value
) {
128 interval_ms
, false, &element_id
, session
, web_view
, params
, value
);
131 Status
ExecuteHoverOverElement(
134 const std::string
& element_id
,
135 const base::DictionaryValue
& params
,
136 scoped_ptr
<base::Value
>* value
) {
138 Status status
= GetElementClickableLocation(
139 session
, web_view
, element_id
, &location
);
140 if (status
.IsError())
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());
150 session
->mouse_position
= location
;
154 Status
ExecuteClickElement(
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())
164 if (tag_name
== "option") {
166 status
= IsOptionElementTogglable(
167 session
, web_view
, element_id
, &is_toggleable
);
168 if (status
.IsError())
171 return ToggleOptionElement(session
, web_view
, element_id
);
173 return SetOptionElementSelected(session
, web_view
, element_id
, true);
176 status
= GetElementClickableLocation(
177 session
, web_view
, element_id
, &location
);
178 if (status
.IsError())
181 std::list
<MouseEvent
> events
;
183 MouseEvent(kMovedMouseEventType
, kNoneMouseButton
,
184 location
.x
, location
.y
, session
->sticky_modifiers
, 0));
186 MouseEvent(kPressedMouseEventType
, kLeftMouseButton
,
187 location
.x
, location
.y
, session
->sticky_modifiers
, 1));
189 MouseEvent(kReleasedMouseEventType
, kLeftMouseButton
,
190 location
.x
, location
.y
, session
->sticky_modifiers
, 1));
192 web_view
->DispatchMouseEvents(events
, session
->GetCurrentFrameId());
194 session
->mouse_position
= location
;
199 Status
ExecuteTouchSingleTap(
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
);
211 Status status
= GetElementClickableLocation(
212 session
, web_view
, element_id
, &location
);
213 if (status
.IsError())
216 std::list
<TouchEvent
> events
;
218 TouchEvent(kTouchStart
, location
.x
, location
.y
));
220 TouchEvent(kTouchEnd
, location
.x
, location
.y
));
221 return web_view
->DispatchTouchEvents(events
);
227 const std::string
& element_id
,
228 const base::DictionaryValue
& params
,
229 scoped_ptr
<base::Value
>* value
) {
231 Status status
= GetElementClickableLocation(
232 session
, web_view
, element_id
, &location
);
233 if (status
.IsError())
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");
244 return Status(kUnknownError
, "'speed' must be a positive integer");
246 status
= web_view
->DispatchTouchEvent(
247 TouchEvent(kTouchStart
, location
.x
, location
.y
));
248 if (status
.IsError())
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())
266 base::PlatformThread::Sleep(
267 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond
));
269 return web_view
->DispatchTouchEvent(
270 TouchEvent(kTouchEnd
, location
.x
+ xoffset
, location
.y
+ yoffset
));
273 Status
ExecuteClearElement(
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
),
288 Status
ExecuteSendKeysToElement(
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");
298 bool is_input
= false;
299 Status status
= IsElementAttributeEqualToIgnoreCase(
300 session
, web_view
, element_id
, "tagName", "input", &is_input
);
301 if (status
.IsError())
303 bool is_file
= false;
304 status
= IsElementAttributeEqualToIgnoreCase(
305 session
, web_view
, element_id
, "type", "file", &is_file
);
306 if (status
.IsError())
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
);
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
]));
325 bool multiple
= false;
326 status
= IsElementAttributeEqualToIgnoreCase(
327 session
, web_view
, element_id
, "multiple", "true", &multiple
);
328 if (status
.IsError())
330 if (!multiple
&& paths
.size() > 1)
331 return Status(kUnknownError
, "the element can not hold multiple files");
333 scoped_ptr
<base::DictionaryValue
> element(CreateElement(element_id
));
334 return web_view
->SetFileInputFiles(
335 session
->GetCurrentFrameId(), *element
, paths
);
337 return SendKeysToElement(session
, web_view
, element_id
, key_list
);
341 Status
ExecuteSubmitElement(
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
),
356 Status
ExecuteGetElementText(
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
),
371 Status
ExecuteGetElementValue(
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'] }",
386 Status
ExecuteGetElementTagName(
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() }",
401 Status
ExecuteIsElementSelected(
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
),
416 Status
ExecuteIsElementEnabled(
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
),
431 Status
ExecuteIsElementDisplayed(
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
),
446 Status
ExecuteGetElementLocation(
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
),
461 Status
ExecuteGetElementLocationOnceScrolledIntoView(
464 const std::string
& element_id
,
465 const base::DictionaryValue
& params
,
466 scoped_ptr
<base::Value
>* value
) {
468 Status status
= ScrollElementIntoView(
469 session
, web_view
, element_id
, &location
);
470 if (status
.IsError())
472 value
->reset(CreateValueFrom(location
));
476 Status
ExecuteGetElementSize(
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
),
491 Status
ExecuteGetElementAttribute(
494 const std::string
& element_id
,
495 const base::DictionaryValue
& params
,
496 scoped_ptr
<base::Value
>* value
) {
498 if (!params
.GetString("name", &name
))
499 return Status(kUnknownError
, "missing 'name'");
500 return GetElementAttribute(session
, web_view
, element_id
, name
, value
);
503 Status
ExecuteGetElementValueOfCSSProperty(
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())
517 value
->reset(new base::StringValue(property_value
));
521 Status
ExecuteElementEquals(
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
));