Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chrome / test / chromedriver / element_commands.cc
blobebb7b97dfc3a2443878f95f6231a4ca667574ddc
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"
7 #include <cmath>
8 #include <list>
9 #include <vector>
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;
31 namespace {
33 Status SendKeysToElement(
34 Session* session,
35 WebView* web_view,
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();
41 while (true) {
42 Status status = IsElementDisplayed(
43 session, web_view, element_id, true, &is_displayed);
44 if (status.IsError())
45 return status;
46 if (is_displayed)
47 break;
48 status = IsElementFocused(session, web_view, element_id, &is_focused);
49 if (status.IsError())
50 return status;
51 if (is_focused)
52 break;
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);
61 if (status.IsError())
62 return status;
63 if (!is_enabled)
64 return Status(kInvalidElementState);
66 if (!is_focused) {
67 base::ListValue args;
68 args.Append(CreateElement(element_id));
69 scoped_ptr<base::Value> result;
70 status = web_view->CallFunction(
71 session->GetCurrentFrameId(), kFocusScript, args, &result);
72 if (status.IsError())
73 return status;
76 return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers);
79 } // namespace
81 Status ExecuteElementCommand(
82 const ElementCommand& command,
83 Session* session,
84 WebView* web_view,
85 const base::DictionaryValue& params,
86 scoped_ptr<base::Value>* value) {
87 std::string id;
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(
94 int interval_ms,
95 Session* session,
96 WebView* web_view,
97 const std::string& element_id,
98 const base::DictionaryValue& params,
99 scoped_ptr<base::Value>* value) {
100 return FindElement(
101 interval_ms, true, &element_id, session, web_view, params, value);
104 Status ExecuteFindChildElements(
105 int interval_ms,
106 Session* session,
107 WebView* web_view,
108 const std::string& element_id,
109 const base::DictionaryValue& params,
110 scoped_ptr<base::Value>* value) {
111 return FindElement(
112 interval_ms, false, &element_id, session, web_view, params, value);
115 Status ExecuteHoverOverElement(
116 Session* session,
117 WebView* web_view,
118 const std::string& element_id,
119 const base::DictionaryValue& params,
120 scoped_ptr<base::Value>* value) {
121 WebPoint location;
122 Status status = GetElementClickableLocation(
123 session, web_view, element_id, &location);
124 if (status.IsError())
125 return status;
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());
133 if (status.IsOk())
134 session->mouse_position = location;
135 return status;
138 Status ExecuteClickElement(
139 Session* session,
140 WebView* web_view,
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())
147 return status;
148 if (tag_name == "option") {
149 bool is_toggleable;
150 status = IsOptionElementTogglable(
151 session, web_view, element_id, &is_toggleable);
152 if (status.IsError())
153 return status;
154 if (is_toggleable)
155 return ToggleOptionElement(session, web_view, element_id);
156 else
157 return SetOptionElementSelected(session, web_view, element_id, true);
158 } else {
159 WebPoint location;
160 status = GetElementClickableLocation(
161 session, web_view, element_id, &location);
162 if (status.IsError())
163 return status;
165 std::list<MouseEvent> events;
166 events.push_back(
167 MouseEvent(kMovedMouseEventType, kNoneMouseButton,
168 location.x, location.y, session->sticky_modifiers, 0));
169 events.push_back(
170 MouseEvent(kPressedMouseEventType, kLeftMouseButton,
171 location.x, location.y, session->sticky_modifiers, 1));
172 events.push_back(
173 MouseEvent(kReleasedMouseEventType, kLeftMouseButton,
174 location.x, location.y, session->sticky_modifiers, 1));
175 status =
176 web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
177 if (status.IsOk())
178 session->mouse_position = location;
179 return status;
183 Status ExecuteTouchSingleTap(
184 Session* session,
185 WebView* web_view,
186 const std::string& element_id,
187 const base::DictionaryValue& params,
188 scoped_ptr<base::Value>* value) {
189 WebPoint location;
190 Status status = GetElementClickableLocation(
191 session, web_view, element_id, &location);
192 if (status.IsError())
193 return status;
194 if (!session->chrome->HasTouchScreen()) {
195 // TODO(samuong): remove this once we stop supporting M44.
196 std::list<TouchEvent> events;
197 events.push_back(
198 TouchEvent(kTouchStart, location.x, location.y));
199 events.push_back(
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(
207 Session* session,
208 WebView* web_view,
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+");
216 WebPoint location;
217 Status status = GetElementClickableLocation(
218 session, web_view, element_id, &location);
219 if (status.IsError())
220 return status;
221 return web_view->SynthesizeTapGesture(location.x, location.y, 2, false);
224 Status ExecuteTouchLongPress(
225 Session* session,
226 WebView* web_view,
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+");
234 WebPoint location;
235 Status status = GetElementClickableLocation(
236 session, web_view, element_id, &location);
237 if (status.IsError())
238 return status;
239 return web_view->SynthesizeTapGesture(location.x, location.y, 1, true);
242 Status ExecuteFlick(
243 Session* session,
244 WebView* web_view,
245 const std::string& element_id,
246 const base::DictionaryValue& params,
247 scoped_ptr<base::Value>* value) {
248 WebPoint location;
249 Status status = GetElementClickableLocation(
250 session, web_view, element_id, &location);
251 if (status.IsError())
252 return status;
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");
261 if (speed < 1)
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())
267 return status;
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())
283 return status;
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(
292 Session* session,
293 WebView* web_view,
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),
303 args, &result);
306 Status ExecuteSendKeysToElement(
307 Session* session,
308 WebView* web_view,
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())
320 return status;
321 bool is_file = false;
322 status = IsElementAttributeEqualToIgnoreCase(
323 session, web_view, element_id, "type", "file", &is_file);
324 if (status.IsError())
325 return status;
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())
347 return status;
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);
354 } else {
355 return SendKeysToElement(session, web_view, element_id, key_list);
359 Status ExecuteSubmitElement(
360 Session* session,
361 WebView* web_view,
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),
370 args,
371 value);
374 Status ExecuteGetElementText(
375 Session* session,
376 WebView* web_view,
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),
385 args,
386 value);
389 Status ExecuteGetElementValue(
390 Session* session,
391 WebView* web_view,
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'] }",
400 args,
401 value);
404 Status ExecuteGetElementTagName(
405 Session* session,
406 WebView* web_view,
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() }",
415 args,
416 value);
419 Status ExecuteIsElementSelected(
420 Session* session,
421 WebView* web_view,
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),
430 args,
431 value);
434 Status ExecuteIsElementEnabled(
435 Session* session,
436 WebView* web_view,
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),
445 args,
446 value);
449 Status ExecuteIsElementDisplayed(
450 Session* session,
451 WebView* web_view,
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),
460 args,
461 value);
464 Status ExecuteGetElementLocation(
465 Session* session,
466 WebView* web_view,
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),
475 args,
476 value);
479 Status ExecuteGetElementLocationOnceScrolledIntoView(
480 Session* session,
481 WebView* web_view,
482 const std::string& element_id,
483 const base::DictionaryValue& params,
484 scoped_ptr<base::Value>* value) {
485 WebPoint offset(0, 0);
486 WebPoint location;
487 Status status = ScrollElementIntoView(
488 session, web_view, element_id, &offset, &location);
489 if (status.IsError())
490 return status;
491 value->reset(CreateValueFrom(location));
492 return Status(kOk);
495 Status ExecuteGetElementSize(
496 Session* session,
497 WebView* web_view,
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),
506 args,
507 value);
510 Status ExecuteGetElementAttribute(
511 Session* session,
512 WebView* web_view,
513 const std::string& element_id,
514 const base::DictionaryValue& params,
515 scoped_ptr<base::Value>* value) {
516 std::string name;
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(
523 Session* session,
524 WebView* web_view,
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())
535 return status;
536 value->reset(new base::StringValue(property_value));
537 return Status(kOk);
540 Status ExecuteElementEquals(
541 Session* session,
542 WebView* web_view,
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));
550 return Status(kOk);