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
);
81 Status
ExecuteElementCommand(
82 const ElementCommand
& command
,
85 const base::DictionaryValue
& params
,
86 scoped_ptr
<base::Value
>* value
) {
88 if (params
.GetString("id", &id
) || params
.GetString("element", &id
))
89 return command
.Run(session
, web_view
, id
, params
, value
);
90 return Status(kUnknownError
, "element identifier must be a string");
93 Status
ExecuteFindChildElement(
97 const std::string
& element_id
,
98 const base::DictionaryValue
& params
,
99 scoped_ptr
<base::Value
>* value
) {
101 interval_ms
, true, &element_id
, session
, web_view
, params
, value
);
104 Status
ExecuteFindChildElements(
108 const std::string
& element_id
,
109 const base::DictionaryValue
& params
,
110 scoped_ptr
<base::Value
>* value
) {
112 interval_ms
, false, &element_id
, session
, web_view
, params
, value
);
115 Status
ExecuteHoverOverElement(
118 const std::string
& element_id
,
119 const base::DictionaryValue
& params
,
120 scoped_ptr
<base::Value
>* value
) {
122 Status status
= GetElementClickableLocation(
123 session
, web_view
, element_id
, &location
);
124 if (status
.IsError())
127 MouseEvent
move_event(
128 kMovedMouseEventType
, kNoneMouseButton
, location
.x
, location
.y
,
129 session
->sticky_modifiers
, 0);
130 std::list
<MouseEvent
> events
;
131 events
.push_back(move_event
);
132 status
= web_view
->DispatchMouseEvents(events
, session
->GetCurrentFrameId());
134 session
->mouse_position
= location
;
138 Status
ExecuteClickElement(
141 const std::string
& element_id
,
142 const base::DictionaryValue
& params
,
143 scoped_ptr
<base::Value
>* value
) {
144 std::string tag_name
;
145 Status status
= GetElementTagName(session
, web_view
, element_id
, &tag_name
);
146 if (status
.IsError())
148 if (tag_name
== "option") {
150 status
= IsOptionElementTogglable(
151 session
, web_view
, element_id
, &is_toggleable
);
152 if (status
.IsError())
155 return ToggleOptionElement(session
, web_view
, element_id
);
157 return SetOptionElementSelected(session
, web_view
, element_id
, true);
160 status
= GetElementClickableLocation(
161 session
, web_view
, element_id
, &location
);
162 if (status
.IsError())
165 std::list
<MouseEvent
> events
;
167 MouseEvent(kMovedMouseEventType
, kNoneMouseButton
,
168 location
.x
, location
.y
, session
->sticky_modifiers
, 0));
170 MouseEvent(kPressedMouseEventType
, kLeftMouseButton
,
171 location
.x
, location
.y
, session
->sticky_modifiers
, 1));
173 MouseEvent(kReleasedMouseEventType
, kLeftMouseButton
,
174 location
.x
, location
.y
, session
->sticky_modifiers
, 1));
176 web_view
->DispatchMouseEvents(events
, session
->GetCurrentFrameId());
178 session
->mouse_position
= location
;
183 Status
ExecuteTouchSingleTap(
186 const std::string
& element_id
,
187 const base::DictionaryValue
& params
,
188 scoped_ptr
<base::Value
>* value
) {
190 Status status
= GetElementClickableLocation(
191 session
, web_view
, element_id
, &location
);
192 if (status
.IsError())
195 std::list
<TouchEvent
> events
;
197 TouchEvent(kTouchStart
, location
.x
, location
.y
));
199 TouchEvent(kTouchEnd
, location
.x
, location
.y
));
200 return web_view
->DispatchTouchEvents(events
);
206 const std::string
& element_id
,
207 const base::DictionaryValue
& params
,
208 scoped_ptr
<base::Value
>* value
) {
210 Status status
= GetElementClickableLocation(
211 session
, web_view
, element_id
, &location
);
212 if (status
.IsError())
215 int xoffset
, yoffset
, speed
;
216 if (!params
.GetInteger("xoffset", &xoffset
))
217 return Status(kUnknownError
, "'xoffset' must be an integer");
218 if (!params
.GetInteger("yoffset", &yoffset
))
219 return Status(kUnknownError
, "'yoffset' must be an integer");
220 if (!params
.GetInteger("speed", &speed
))
221 return Status(kUnknownError
, "'speed' must be an integer");
223 return Status(kUnknownError
, "'speed' must be a positive integer");
225 status
= web_view
->DispatchTouchEvent(
226 TouchEvent(kTouchStart
, location
.x
, location
.y
));
227 if (status
.IsError())
230 const double offset
=
231 std::sqrt(static_cast<double>(xoffset
* xoffset
+ yoffset
* yoffset
));
232 const double xoffset_per_event
=
233 (speed
* xoffset
) / (kFlickTouchEventsPerSecond
* offset
);
234 const double yoffset_per_event
=
235 (speed
* yoffset
) / (kFlickTouchEventsPerSecond
* offset
);
236 const int total_events
=
237 (offset
* kFlickTouchEventsPerSecond
) / speed
;
238 for (int i
= 0; i
< total_events
; i
++) {
239 status
= web_view
->DispatchTouchEvent(
240 TouchEvent(kTouchMove
,
241 location
.x
+ xoffset_per_event
* i
,
242 location
.y
+ yoffset_per_event
* i
));
243 if (status
.IsError())
245 base::PlatformThread::Sleep(
246 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond
));
248 return web_view
->DispatchTouchEvent(
249 TouchEvent(kTouchEnd
, location
.x
+ xoffset
, location
.y
+ yoffset
));
252 Status
ExecuteClearElement(
255 const std::string
& element_id
,
256 const base::DictionaryValue
& params
,
257 scoped_ptr
<base::Value
>* value
) {
258 base::ListValue args
;
259 args
.Append(CreateElement(element_id
));
260 scoped_ptr
<base::Value
> result
;
261 return web_view
->CallFunction(
262 session
->GetCurrentFrameId(),
263 webdriver::atoms::asString(webdriver::atoms::CLEAR
),
267 Status
ExecuteSendKeysToElement(
270 const std::string
& element_id
,
271 const base::DictionaryValue
& params
,
272 scoped_ptr
<base::Value
>* value
) {
273 const base::ListValue
* key_list
;
274 if (!params
.GetList("value", &key_list
))
275 return Status(kUnknownError
, "'value' must be a list");
277 bool is_input
= false;
278 Status status
= IsElementAttributeEqualToIgnoreCase(
279 session
, web_view
, element_id
, "tagName", "input", &is_input
);
280 if (status
.IsError())
282 bool is_file
= false;
283 status
= IsElementAttributeEqualToIgnoreCase(
284 session
, web_view
, element_id
, "type", "file", &is_file
);
285 if (status
.IsError())
287 if (is_input
&& is_file
) {
288 // Compress array into a single string.
289 base::FilePath::StringType paths_string
;
290 for (size_t i
= 0; i
< key_list
->GetSize(); ++i
) {
291 base::FilePath::StringType path_part
;
292 if (!key_list
->GetString(i
, &path_part
))
293 return Status(kUnknownError
, "'value' is invalid");
294 paths_string
.append(path_part
);
297 // Separate the string into separate paths, delimited by '\n'.
298 std::vector
<base::FilePath::StringType
> path_strings
;
299 base::SplitString(paths_string
, '\n', &path_strings
);
300 std::vector
<base::FilePath
> paths
;
301 for (size_t i
= 0; i
< path_strings
.size(); ++i
)
302 paths
.push_back(base::FilePath(path_strings
[i
]));
304 bool multiple
= false;
305 status
= IsElementAttributeEqualToIgnoreCase(
306 session
, web_view
, element_id
, "multiple", "true", &multiple
);
307 if (status
.IsError())
309 if (!multiple
&& paths
.size() > 1)
310 return Status(kUnknownError
, "the element can not hold multiple files");
312 scoped_ptr
<base::DictionaryValue
> element(CreateElement(element_id
));
313 return web_view
->SetFileInputFiles(
314 session
->GetCurrentFrameId(), *element
, paths
);
316 return SendKeysToElement(session
, web_view
, element_id
, key_list
);
320 Status
ExecuteSubmitElement(
323 const std::string
& element_id
,
324 const base::DictionaryValue
& params
,
325 scoped_ptr
<base::Value
>* value
) {
326 base::ListValue args
;
327 args
.Append(CreateElement(element_id
));
328 return web_view
->CallFunction(
329 session
->GetCurrentFrameId(),
330 webdriver::atoms::asString(webdriver::atoms::SUBMIT
),
335 Status
ExecuteGetElementText(
338 const std::string
& element_id
,
339 const base::DictionaryValue
& params
,
340 scoped_ptr
<base::Value
>* value
) {
341 base::ListValue args
;
342 args
.Append(CreateElement(element_id
));
343 return web_view
->CallFunction(
344 session
->GetCurrentFrameId(),
345 webdriver::atoms::asString(webdriver::atoms::GET_TEXT
),
350 Status
ExecuteGetElementValue(
353 const std::string
& element_id
,
354 const base::DictionaryValue
& params
,
355 scoped_ptr
<base::Value
>* value
) {
356 base::ListValue args
;
357 args
.Append(CreateElement(element_id
));
358 return web_view
->CallFunction(
359 session
->GetCurrentFrameId(),
360 "function(elem) { return elem['value'] }",
365 Status
ExecuteGetElementTagName(
368 const std::string
& element_id
,
369 const base::DictionaryValue
& params
,
370 scoped_ptr
<base::Value
>* value
) {
371 base::ListValue args
;
372 args
.Append(CreateElement(element_id
));
373 return web_view
->CallFunction(
374 session
->GetCurrentFrameId(),
375 "function(elem) { return elem.tagName.toLowerCase() }",
380 Status
ExecuteIsElementSelected(
383 const std::string
& element_id
,
384 const base::DictionaryValue
& params
,
385 scoped_ptr
<base::Value
>* value
) {
386 base::ListValue args
;
387 args
.Append(CreateElement(element_id
));
388 return web_view
->CallFunction(
389 session
->GetCurrentFrameId(),
390 webdriver::atoms::asString(webdriver::atoms::IS_SELECTED
),
395 Status
ExecuteIsElementEnabled(
398 const std::string
& element_id
,
399 const base::DictionaryValue
& params
,
400 scoped_ptr
<base::Value
>* value
) {
401 base::ListValue args
;
402 args
.Append(CreateElement(element_id
));
403 return web_view
->CallFunction(
404 session
->GetCurrentFrameId(),
405 webdriver::atoms::asString(webdriver::atoms::IS_ENABLED
),
410 Status
ExecuteIsElementDisplayed(
413 const std::string
& element_id
,
414 const base::DictionaryValue
& params
,
415 scoped_ptr
<base::Value
>* value
) {
416 base::ListValue args
;
417 args
.Append(CreateElement(element_id
));
418 return web_view
->CallFunction(
419 session
->GetCurrentFrameId(),
420 webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED
),
425 Status
ExecuteGetElementLocation(
428 const std::string
& element_id
,
429 const base::DictionaryValue
& params
,
430 scoped_ptr
<base::Value
>* value
) {
431 base::ListValue args
;
432 args
.Append(CreateElement(element_id
));
433 return web_view
->CallFunction(
434 session
->GetCurrentFrameId(),
435 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION
),
440 Status
ExecuteGetElementLocationOnceScrolledIntoView(
443 const std::string
& element_id
,
444 const base::DictionaryValue
& params
,
445 scoped_ptr
<base::Value
>* value
) {
446 WebPoint
offset(0, 0);
448 Status status
= ScrollElementIntoView(
449 session
, web_view
, element_id
, &offset
, &location
);
450 if (status
.IsError())
452 value
->reset(CreateValueFrom(location
));
456 Status
ExecuteGetElementSize(
459 const std::string
& element_id
,
460 const base::DictionaryValue
& params
,
461 scoped_ptr
<base::Value
>* value
) {
462 base::ListValue args
;
463 args
.Append(CreateElement(element_id
));
464 return web_view
->CallFunction(
465 session
->GetCurrentFrameId(),
466 webdriver::atoms::asString(webdriver::atoms::GET_SIZE
),
471 Status
ExecuteGetElementAttribute(
474 const std::string
& element_id
,
475 const base::DictionaryValue
& params
,
476 scoped_ptr
<base::Value
>* value
) {
478 if (!params
.GetString("name", &name
))
479 return Status(kUnknownError
, "missing 'name'");
480 return GetElementAttribute(session
, web_view
, element_id
, name
, value
);
483 Status
ExecuteGetElementValueOfCSSProperty(
486 const std::string
& element_id
,
487 const base::DictionaryValue
& params
,
488 scoped_ptr
<base::Value
>* value
) {
489 std::string property_name
;
490 if (!params
.GetString("propertyName", &property_name
))
491 return Status(kUnknownError
, "missing 'propertyName'");
492 std::string property_value
;
493 Status status
= GetElementEffectiveStyle(
494 session
, web_view
, element_id
, property_name
, &property_value
);
495 if (status
.IsError())
497 value
->reset(new base::StringValue(property_value
));
501 Status
ExecuteElementEquals(
504 const std::string
& element_id
,
505 const base::DictionaryValue
& params
,
506 scoped_ptr
<base::Value
>* value
) {
507 std::string other_element_id
;
508 if (!params
.GetString("other", &other_element_id
))
509 return Status(kUnknownError
, "'other' must be a string");
510 value
->reset(new base::FundamentalValue(element_id
== other_element_id
));