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())
194 if (!session
->chrome
->HasTouchScreen()) {
195 // TODO(samuong): remove this once we stop supporting M44.
196 std::list
<TouchEvent
> events
;
198 TouchEvent(kTouchStart
, location
.x
, location
.y
));
200 TouchEvent(kTouchEnd
, location
.x
, location
.y
));
201 return web_view
->DispatchTouchEvents(events
);
203 return web_view
->SynthesizeTapGesture(location
.x
, location
.y
, 1, false);
206 Status
ExecuteTouchDoubleTap(
209 const std::string
& element_id
,
210 const base::DictionaryValue
& params
,
211 scoped_ptr
<base::Value
>* value
) {
212 if (!session
->chrome
->HasTouchScreen()) {
213 // TODO(samuong): remove this once we stop supporting M44.
214 return Status(kUnknownCommand
, "Double tap command requires Chrome 44+");
217 Status status
= GetElementClickableLocation(
218 session
, web_view
, element_id
, &location
);
219 if (status
.IsError())
221 return web_view
->SynthesizeTapGesture(location
.x
, location
.y
, 2, false);
224 Status
ExecuteTouchLongPress(
227 const std::string
& element_id
,
228 const base::DictionaryValue
& params
,
229 scoped_ptr
<base::Value
>* value
) {
230 if (!session
->chrome
->HasTouchScreen()) {
231 // TODO(samuong): remove this once we stop supporting M44.
232 return Status(kUnknownCommand
, "Long press command requires Chrome 44+");
235 Status status
= GetElementClickableLocation(
236 session
, web_view
, element_id
, &location
);
237 if (status
.IsError())
239 return web_view
->SynthesizeTapGesture(location
.x
, location
.y
, 1, true);
245 const std::string
& element_id
,
246 const base::DictionaryValue
& params
,
247 scoped_ptr
<base::Value
>* value
) {
249 Status status
= GetElementClickableLocation(
250 session
, web_view
, element_id
, &location
);
251 if (status
.IsError())
254 int xoffset
, yoffset
, speed
;
255 if (!params
.GetInteger("xoffset", &xoffset
))
256 return Status(kUnknownError
, "'xoffset' must be an integer");
257 if (!params
.GetInteger("yoffset", &yoffset
))
258 return Status(kUnknownError
, "'yoffset' must be an integer");
259 if (!params
.GetInteger("speed", &speed
))
260 return Status(kUnknownError
, "'speed' must be an integer");
262 return Status(kUnknownError
, "'speed' must be a positive integer");
264 status
= web_view
->DispatchTouchEvent(
265 TouchEvent(kTouchStart
, location
.x
, location
.y
));
266 if (status
.IsError())
269 const double offset
=
270 std::sqrt(static_cast<double>(xoffset
* xoffset
+ yoffset
* yoffset
));
271 const double xoffset_per_event
=
272 (speed
* xoffset
) / (kFlickTouchEventsPerSecond
* offset
);
273 const double yoffset_per_event
=
274 (speed
* yoffset
) / (kFlickTouchEventsPerSecond
* offset
);
275 const int total_events
=
276 (offset
* kFlickTouchEventsPerSecond
) / speed
;
277 for (int i
= 0; i
< total_events
; i
++) {
278 status
= web_view
->DispatchTouchEvent(
279 TouchEvent(kTouchMove
,
280 location
.x
+ xoffset_per_event
* i
,
281 location
.y
+ yoffset_per_event
* i
));
282 if (status
.IsError())
284 base::PlatformThread::Sleep(
285 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond
));
287 return web_view
->DispatchTouchEvent(
288 TouchEvent(kTouchEnd
, location
.x
+ xoffset
, location
.y
+ yoffset
));
291 Status
ExecuteClearElement(
294 const std::string
& element_id
,
295 const base::DictionaryValue
& params
,
296 scoped_ptr
<base::Value
>* value
) {
297 base::ListValue args
;
298 args
.Append(CreateElement(element_id
));
299 scoped_ptr
<base::Value
> result
;
300 return web_view
->CallFunction(
301 session
->GetCurrentFrameId(),
302 webdriver::atoms::asString(webdriver::atoms::CLEAR
),
306 Status
ExecuteSendKeysToElement(
309 const std::string
& element_id
,
310 const base::DictionaryValue
& params
,
311 scoped_ptr
<base::Value
>* value
) {
312 const base::ListValue
* key_list
;
313 if (!params
.GetList("value", &key_list
))
314 return Status(kUnknownError
, "'value' must be a list");
316 bool is_input
= false;
317 Status status
= IsElementAttributeEqualToIgnoreCase(
318 session
, web_view
, element_id
, "tagName", "input", &is_input
);
319 if (status
.IsError())
321 bool is_file
= false;
322 status
= IsElementAttributeEqualToIgnoreCase(
323 session
, web_view
, element_id
, "type", "file", &is_file
);
324 if (status
.IsError())
326 if (is_input
&& is_file
) {
327 // Compress array into a single string.
328 base::FilePath::StringType paths_string
;
329 for (size_t i
= 0; i
< key_list
->GetSize(); ++i
) {
330 base::FilePath::StringType path_part
;
331 if (!key_list
->GetString(i
, &path_part
))
332 return Status(kUnknownError
, "'value' is invalid");
333 paths_string
.append(path_part
);
336 // Separate the string into separate paths, delimited by '\n'.
337 std::vector
<base::FilePath
> paths
;
338 for (const auto& path_piece
: base::SplitStringPiece(
339 paths_string
, base::FilePath::StringType(1, '\n'),
340 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
))
341 paths
.push_back(base::FilePath(path_piece
));
343 bool multiple
= false;
344 status
= IsElementAttributeEqualToIgnoreCase(
345 session
, web_view
, element_id
, "multiple", "true", &multiple
);
346 if (status
.IsError())
348 if (!multiple
&& paths
.size() > 1)
349 return Status(kUnknownError
, "the element can not hold multiple files");
351 scoped_ptr
<base::DictionaryValue
> element(CreateElement(element_id
));
352 return web_view
->SetFileInputFiles(
353 session
->GetCurrentFrameId(), *element
, paths
);
355 return SendKeysToElement(session
, web_view
, element_id
, key_list
);
359 Status
ExecuteSubmitElement(
362 const std::string
& element_id
,
363 const base::DictionaryValue
& params
,
364 scoped_ptr
<base::Value
>* value
) {
365 base::ListValue args
;
366 args
.Append(CreateElement(element_id
));
367 return web_view
->CallFunction(
368 session
->GetCurrentFrameId(),
369 webdriver::atoms::asString(webdriver::atoms::SUBMIT
),
374 Status
ExecuteGetElementText(
377 const std::string
& element_id
,
378 const base::DictionaryValue
& params
,
379 scoped_ptr
<base::Value
>* value
) {
380 base::ListValue args
;
381 args
.Append(CreateElement(element_id
));
382 return web_view
->CallFunction(
383 session
->GetCurrentFrameId(),
384 webdriver::atoms::asString(webdriver::atoms::GET_TEXT
),
389 Status
ExecuteGetElementValue(
392 const std::string
& element_id
,
393 const base::DictionaryValue
& params
,
394 scoped_ptr
<base::Value
>* value
) {
395 base::ListValue args
;
396 args
.Append(CreateElement(element_id
));
397 return web_view
->CallFunction(
398 session
->GetCurrentFrameId(),
399 "function(elem) { return elem['value'] }",
404 Status
ExecuteGetElementTagName(
407 const std::string
& element_id
,
408 const base::DictionaryValue
& params
,
409 scoped_ptr
<base::Value
>* value
) {
410 base::ListValue args
;
411 args
.Append(CreateElement(element_id
));
412 return web_view
->CallFunction(
413 session
->GetCurrentFrameId(),
414 "function(elem) { return elem.tagName.toLowerCase() }",
419 Status
ExecuteIsElementSelected(
422 const std::string
& element_id
,
423 const base::DictionaryValue
& params
,
424 scoped_ptr
<base::Value
>* value
) {
425 base::ListValue args
;
426 args
.Append(CreateElement(element_id
));
427 return web_view
->CallFunction(
428 session
->GetCurrentFrameId(),
429 webdriver::atoms::asString(webdriver::atoms::IS_SELECTED
),
434 Status
ExecuteIsElementEnabled(
437 const std::string
& element_id
,
438 const base::DictionaryValue
& params
,
439 scoped_ptr
<base::Value
>* value
) {
440 base::ListValue args
;
441 args
.Append(CreateElement(element_id
));
442 return web_view
->CallFunction(
443 session
->GetCurrentFrameId(),
444 webdriver::atoms::asString(webdriver::atoms::IS_ENABLED
),
449 Status
ExecuteIsElementDisplayed(
452 const std::string
& element_id
,
453 const base::DictionaryValue
& params
,
454 scoped_ptr
<base::Value
>* value
) {
455 base::ListValue args
;
456 args
.Append(CreateElement(element_id
));
457 return web_view
->CallFunction(
458 session
->GetCurrentFrameId(),
459 webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED
),
464 Status
ExecuteGetElementLocation(
467 const std::string
& element_id
,
468 const base::DictionaryValue
& params
,
469 scoped_ptr
<base::Value
>* value
) {
470 base::ListValue args
;
471 args
.Append(CreateElement(element_id
));
472 return web_view
->CallFunction(
473 session
->GetCurrentFrameId(),
474 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION
),
479 Status
ExecuteGetElementLocationOnceScrolledIntoView(
482 const std::string
& element_id
,
483 const base::DictionaryValue
& params
,
484 scoped_ptr
<base::Value
>* value
) {
485 WebPoint
offset(0, 0);
487 Status status
= ScrollElementIntoView(
488 session
, web_view
, element_id
, &offset
, &location
);
489 if (status
.IsError())
491 value
->reset(CreateValueFrom(location
));
495 Status
ExecuteGetElementSize(
498 const std::string
& element_id
,
499 const base::DictionaryValue
& params
,
500 scoped_ptr
<base::Value
>* value
) {
501 base::ListValue args
;
502 args
.Append(CreateElement(element_id
));
503 return web_view
->CallFunction(
504 session
->GetCurrentFrameId(),
505 webdriver::atoms::asString(webdriver::atoms::GET_SIZE
),
510 Status
ExecuteGetElementAttribute(
513 const std::string
& element_id
,
514 const base::DictionaryValue
& params
,
515 scoped_ptr
<base::Value
>* value
) {
517 if (!params
.GetString("name", &name
))
518 return Status(kUnknownError
, "missing 'name'");
519 return GetElementAttribute(session
, web_view
, element_id
, name
, value
);
522 Status
ExecuteGetElementValueOfCSSProperty(
525 const std::string
& element_id
,
526 const base::DictionaryValue
& params
,
527 scoped_ptr
<base::Value
>* value
) {
528 std::string property_name
;
529 if (!params
.GetString("propertyName", &property_name
))
530 return Status(kUnknownError
, "missing 'propertyName'");
531 std::string property_value
;
532 Status status
= GetElementEffectiveStyle(
533 session
, web_view
, element_id
, property_name
, &property_value
);
534 if (status
.IsError())
536 value
->reset(new base::StringValue(property_value
));
540 Status
ExecuteElementEquals(
543 const std::string
& element_id
,
544 const base::DictionaryValue
& params
,
545 scoped_ptr
<base::Value
>* value
) {
546 std::string other_element_id
;
547 if (!params
.GetString("other", &other_element_id
))
548 return Status(kUnknownError
, "'other' must be a string");
549 value
->reset(new base::FundamentalValue(element_id
== other_element_id
));