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/chrome.h"
20 #include "chrome/test/chromedriver/chrome/js.h"
21 #include "chrome/test/chromedriver/chrome/status.h"
22 #include "chrome/test/chromedriver/chrome/ui_events.h"
23 #include "chrome/test/chromedriver/chrome/web_view.h"
24 #include "chrome/test/chromedriver/element_util.h"
25 #include "chrome/test/chromedriver/session.h"
26 #include "chrome/test/chromedriver/util.h"
27 #include "third_party/webdriver/atoms.h"
29 const int kFlickTouchEventsPerSecond
= 30;
33 Status
SendKeysToElement(
36 const std::string
& element_id
,
37 const base::ListValue
* key_list
) {
38 bool is_displayed
= false;
39 bool is_focused
= false;
40 base::TimeTicks start_time
= base::TimeTicks::Now();
42 Status status
= IsElementDisplayed(
43 session
, web_view
, element_id
, true, &is_displayed
);
48 status
= IsElementFocused(session
, web_view
, element_id
, &is_focused
);
53 if (base::TimeTicks::Now() - start_time
>= session
->implicit_wait
) {
54 return Status(kElementNotVisible
);
56 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
59 bool is_enabled
= false;
60 Status status
= IsElementEnabled(session
, web_view
, element_id
, &is_enabled
);
64 return Status(kInvalidElementState
);
68 args
.Append(CreateElement(element_id
));
69 scoped_ptr
<base::Value
> result
;
70 status
= web_view
->CallFunction(
71 session
->GetCurrentFrameId(), kFocusScript
, args
, &result
);
76 return SendKeysOnWindow(web_view
, key_list
, true, &session
->sticky_modifiers
);
79 Status
ExecuteTouchSingleTapAtom(
82 const std::string
& element_id
,
83 const base::DictionaryValue
& params
,
84 scoped_ptr
<base::Value
>* value
) {
86 args
.Append(CreateElement(element_id
));
87 return web_view
->CallFunction(
88 session
->GetCurrentFrameId(),
89 webdriver::atoms::asString(webdriver::atoms::TOUCH_SINGLE_TAP
),
96 Status
ExecuteElementCommand(
97 const ElementCommand
& command
,
100 const base::DictionaryValue
& params
,
101 scoped_ptr
<base::Value
>* value
) {
103 if (params
.GetString("id", &id
) || params
.GetString("element", &id
))
104 return command
.Run(session
, web_view
, id
, params
, value
);
105 return Status(kUnknownError
, "element identifier must be a string");
108 Status
ExecuteFindChildElement(
112 const std::string
& element_id
,
113 const base::DictionaryValue
& params
,
114 scoped_ptr
<base::Value
>* value
) {
116 interval_ms
, true, &element_id
, session
, web_view
, params
, value
);
119 Status
ExecuteFindChildElements(
123 const std::string
& element_id
,
124 const base::DictionaryValue
& params
,
125 scoped_ptr
<base::Value
>* value
) {
127 interval_ms
, false, &element_id
, session
, web_view
, params
, value
);
130 Status
ExecuteHoverOverElement(
133 const std::string
& element_id
,
134 const base::DictionaryValue
& params
,
135 scoped_ptr
<base::Value
>* value
) {
137 Status status
= GetElementClickableLocation(
138 session
, web_view
, element_id
, &location
);
139 if (status
.IsError())
142 MouseEvent
move_event(
143 kMovedMouseEventType
, kNoneMouseButton
, location
.x
, location
.y
,
144 session
->sticky_modifiers
, 0);
145 std::list
<MouseEvent
> events
;
146 events
.push_back(move_event
);
147 status
= web_view
->DispatchMouseEvents(events
, session
->GetCurrentFrameId());
149 session
->mouse_position
= location
;
153 Status
ExecuteClickElement(
156 const std::string
& element_id
,
157 const base::DictionaryValue
& params
,
158 scoped_ptr
<base::Value
>* value
) {
159 std::string tag_name
;
160 Status status
= GetElementTagName(session
, web_view
, element_id
, &tag_name
);
161 if (status
.IsError())
163 if (tag_name
== "option") {
165 status
= IsOptionElementTogglable(
166 session
, web_view
, element_id
, &is_toggleable
);
167 if (status
.IsError())
170 return ToggleOptionElement(session
, web_view
, element_id
);
172 return SetOptionElementSelected(session
, web_view
, element_id
, true);
175 status
= GetElementClickableLocation(
176 session
, web_view
, element_id
, &location
);
177 if (status
.IsError())
180 std::list
<MouseEvent
> events
;
182 MouseEvent(kMovedMouseEventType
, kNoneMouseButton
,
183 location
.x
, location
.y
, session
->sticky_modifiers
, 0));
185 MouseEvent(kPressedMouseEventType
, kLeftMouseButton
,
186 location
.x
, location
.y
, session
->sticky_modifiers
, 1));
188 MouseEvent(kReleasedMouseEventType
, kLeftMouseButton
,
189 location
.x
, location
.y
, session
->sticky_modifiers
, 1));
191 web_view
->DispatchMouseEvents(events
, session
->GetCurrentFrameId());
193 session
->mouse_position
= location
;
198 Status
ExecuteTouchSingleTap(
201 const std::string
& element_id
,
202 const base::DictionaryValue
& params
,
203 scoped_ptr
<base::Value
>* value
) {
204 // Fall back to javascript atom for pre-m30 Chrome.
205 if (session
->chrome
->GetBuildNo() < 1576)
206 return ExecuteTouchSingleTapAtom(
207 session
, web_view
, element_id
, params
, value
);
210 Status status
= GetElementClickableLocation(
211 session
, web_view
, element_id
, &location
);
212 if (status
.IsError())
215 std::list
<TouchEvent
> events
;
217 TouchEvent(kTouchStart
, location
.x
, location
.y
));
219 TouchEvent(kTouchEnd
, location
.x
, location
.y
));
220 return web_view
->DispatchTouchEvents(events
);
226 const std::string
& element_id
,
227 const base::DictionaryValue
& params
,
228 scoped_ptr
<base::Value
>* value
) {
230 Status status
= GetElementClickableLocation(
231 session
, web_view
, element_id
, &location
);
232 if (status
.IsError())
235 int xoffset
, yoffset
, speed
;
236 if (!params
.GetInteger("xoffset", &xoffset
))
237 return Status(kUnknownError
, "'xoffset' must be an integer");
238 if (!params
.GetInteger("yoffset", &yoffset
))
239 return Status(kUnknownError
, "'yoffset' must be an integer");
240 if (!params
.GetInteger("speed", &speed
))
241 return Status(kUnknownError
, "'speed' must be an integer");
243 return Status(kUnknownError
, "'speed' must be a positive integer");
245 status
= web_view
->DispatchTouchEvent(
246 TouchEvent(kTouchStart
, location
.x
, location
.y
));
247 if (status
.IsError())
250 const double offset
=
251 std::sqrt(static_cast<double>(xoffset
* xoffset
+ yoffset
* yoffset
));
252 const double xoffset_per_event
=
253 (speed
* xoffset
) / (kFlickTouchEventsPerSecond
* offset
);
254 const double yoffset_per_event
=
255 (speed
* yoffset
) / (kFlickTouchEventsPerSecond
* offset
);
256 const int total_events
=
257 (offset
* kFlickTouchEventsPerSecond
) / speed
;
258 for (int i
= 0; i
< total_events
; i
++) {
259 status
= web_view
->DispatchTouchEvent(
260 TouchEvent(kTouchMove
,
261 location
.x
+ xoffset_per_event
* i
,
262 location
.y
+ yoffset_per_event
* i
));
263 if (status
.IsError())
265 base::PlatformThread::Sleep(
266 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond
));
268 return web_view
->DispatchTouchEvent(
269 TouchEvent(kTouchEnd
, location
.x
+ xoffset
, location
.y
+ yoffset
));
272 Status
ExecuteClearElement(
275 const std::string
& element_id
,
276 const base::DictionaryValue
& params
,
277 scoped_ptr
<base::Value
>* value
) {
278 base::ListValue args
;
279 args
.Append(CreateElement(element_id
));
280 scoped_ptr
<base::Value
> result
;
281 return web_view
->CallFunction(
282 session
->GetCurrentFrameId(),
283 webdriver::atoms::asString(webdriver::atoms::CLEAR
),
287 Status
ExecuteSendKeysToElement(
290 const std::string
& element_id
,
291 const base::DictionaryValue
& params
,
292 scoped_ptr
<base::Value
>* value
) {
293 const base::ListValue
* key_list
;
294 if (!params
.GetList("value", &key_list
))
295 return Status(kUnknownError
, "'value' must be a list");
297 bool is_input
= false;
298 Status status
= IsElementAttributeEqualToIgnoreCase(
299 session
, web_view
, element_id
, "tagName", "input", &is_input
);
300 if (status
.IsError())
302 bool is_file
= false;
303 status
= IsElementAttributeEqualToIgnoreCase(
304 session
, web_view
, element_id
, "type", "file", &is_file
);
305 if (status
.IsError())
307 if (is_input
&& is_file
) {
308 // Compress array into a single string.
309 base::FilePath::StringType paths_string
;
310 for (size_t i
= 0; i
< key_list
->GetSize(); ++i
) {
311 base::FilePath::StringType path_part
;
312 if (!key_list
->GetString(i
, &path_part
))
313 return Status(kUnknownError
, "'value' is invalid");
314 paths_string
.append(path_part
);
317 // Separate the string into separate paths, delimited by '\n'.
318 std::vector
<base::FilePath::StringType
> path_strings
;
319 base::SplitString(paths_string
, '\n', &path_strings
);
320 std::vector
<base::FilePath
> paths
;
321 for (size_t i
= 0; i
< path_strings
.size(); ++i
)
322 paths
.push_back(base::FilePath(path_strings
[i
]));
324 bool multiple
= false;
325 status
= IsElementAttributeEqualToIgnoreCase(
326 session
, web_view
, element_id
, "multiple", "true", &multiple
);
327 if (status
.IsError())
329 if (!multiple
&& paths
.size() > 1)
330 return Status(kUnknownError
, "the element can not hold multiple files");
332 scoped_ptr
<base::DictionaryValue
> element(CreateElement(element_id
));
333 return web_view
->SetFileInputFiles(
334 session
->GetCurrentFrameId(), *element
, paths
);
336 return SendKeysToElement(session
, web_view
, element_id
, key_list
);
340 Status
ExecuteSubmitElement(
343 const std::string
& element_id
,
344 const base::DictionaryValue
& params
,
345 scoped_ptr
<base::Value
>* value
) {
346 base::ListValue args
;
347 args
.Append(CreateElement(element_id
));
348 return web_view
->CallFunction(
349 session
->GetCurrentFrameId(),
350 webdriver::atoms::asString(webdriver::atoms::SUBMIT
),
355 Status
ExecuteGetElementText(
358 const std::string
& element_id
,
359 const base::DictionaryValue
& params
,
360 scoped_ptr
<base::Value
>* value
) {
361 base::ListValue args
;
362 args
.Append(CreateElement(element_id
));
363 return web_view
->CallFunction(
364 session
->GetCurrentFrameId(),
365 webdriver::atoms::asString(webdriver::atoms::GET_TEXT
),
370 Status
ExecuteGetElementValue(
373 const std::string
& element_id
,
374 const base::DictionaryValue
& params
,
375 scoped_ptr
<base::Value
>* value
) {
376 base::ListValue args
;
377 args
.Append(CreateElement(element_id
));
378 return web_view
->CallFunction(
379 session
->GetCurrentFrameId(),
380 "function(elem) { return elem['value'] }",
385 Status
ExecuteGetElementTagName(
388 const std::string
& element_id
,
389 const base::DictionaryValue
& params
,
390 scoped_ptr
<base::Value
>* value
) {
391 base::ListValue args
;
392 args
.Append(CreateElement(element_id
));
393 return web_view
->CallFunction(
394 session
->GetCurrentFrameId(),
395 "function(elem) { return elem.tagName.toLowerCase() }",
400 Status
ExecuteIsElementSelected(
403 const std::string
& element_id
,
404 const base::DictionaryValue
& params
,
405 scoped_ptr
<base::Value
>* value
) {
406 base::ListValue args
;
407 args
.Append(CreateElement(element_id
));
408 return web_view
->CallFunction(
409 session
->GetCurrentFrameId(),
410 webdriver::atoms::asString(webdriver::atoms::IS_SELECTED
),
415 Status
ExecuteIsElementEnabled(
418 const std::string
& element_id
,
419 const base::DictionaryValue
& params
,
420 scoped_ptr
<base::Value
>* value
) {
421 base::ListValue args
;
422 args
.Append(CreateElement(element_id
));
423 return web_view
->CallFunction(
424 session
->GetCurrentFrameId(),
425 webdriver::atoms::asString(webdriver::atoms::IS_ENABLED
),
430 Status
ExecuteIsElementDisplayed(
433 const std::string
& element_id
,
434 const base::DictionaryValue
& params
,
435 scoped_ptr
<base::Value
>* value
) {
436 base::ListValue args
;
437 args
.Append(CreateElement(element_id
));
438 return web_view
->CallFunction(
439 session
->GetCurrentFrameId(),
440 webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED
),
445 Status
ExecuteGetElementLocation(
448 const std::string
& element_id
,
449 const base::DictionaryValue
& params
,
450 scoped_ptr
<base::Value
>* value
) {
451 base::ListValue args
;
452 args
.Append(CreateElement(element_id
));
453 return web_view
->CallFunction(
454 session
->GetCurrentFrameId(),
455 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION
),
460 Status
ExecuteGetElementLocationOnceScrolledIntoView(
463 const std::string
& element_id
,
464 const base::DictionaryValue
& params
,
465 scoped_ptr
<base::Value
>* value
) {
467 Status status
= ScrollElementIntoView(
468 session
, web_view
, element_id
, &location
);
469 if (status
.IsError())
471 value
->reset(CreateValueFrom(location
));
475 Status
ExecuteGetElementSize(
478 const std::string
& element_id
,
479 const base::DictionaryValue
& params
,
480 scoped_ptr
<base::Value
>* value
) {
481 base::ListValue args
;
482 args
.Append(CreateElement(element_id
));
483 return web_view
->CallFunction(
484 session
->GetCurrentFrameId(),
485 webdriver::atoms::asString(webdriver::atoms::GET_SIZE
),
490 Status
ExecuteGetElementAttribute(
493 const std::string
& element_id
,
494 const base::DictionaryValue
& params
,
495 scoped_ptr
<base::Value
>* value
) {
497 if (!params
.GetString("name", &name
))
498 return Status(kUnknownError
, "missing 'name'");
499 return GetElementAttribute(session
, web_view
, element_id
, name
, value
);
502 Status
ExecuteGetElementValueOfCSSProperty(
505 const std::string
& element_id
,
506 const base::DictionaryValue
& params
,
507 scoped_ptr
<base::Value
>* value
) {
508 std::string property_name
;
509 if (!params
.GetString("propertyName", &property_name
))
510 return Status(kUnknownError
, "missing 'propertyName'");
511 std::string property_value
;
512 Status status
= GetElementEffectiveStyle(
513 session
, web_view
, element_id
, property_name
, &property_value
);
514 if (status
.IsError())
516 value
->reset(new base::StringValue(property_value
));
520 Status
ExecuteElementEquals(
523 const std::string
& element_id
,
524 const base::DictionaryValue
& params
,
525 scoped_ptr
<base::Value
>* value
) {
526 std::string other_element_id
;
527 if (!params
.GetString("other", &other_element_id
))
528 return Status(kUnknownError
, "'other' must be a string");
529 value
->reset(new base::FundamentalValue(element_id
== other_element_id
));