Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chrome / test / chromedriver / element_util.cc
blobe36d83b00cf4027e4397a55760e3be8c2d48841b
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_util.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/threading/platform_thread.h"
11 #include "base/time/time.h"
12 #include "base/values.h"
13 #include "chrome/test/chromedriver/basic_types.h"
14 #include "chrome/test/chromedriver/chrome/chrome.h"
15 #include "chrome/test/chromedriver/chrome/js.h"
16 #include "chrome/test/chromedriver/chrome/status.h"
17 #include "chrome/test/chromedriver/chrome/web_view.h"
18 #include "chrome/test/chromedriver/session.h"
19 #include "third_party/webdriver/atoms.h"
21 namespace {
23 const char kElementKey[] = "ELEMENT";
25 bool ParseFromValue(base::Value* value, WebPoint* point) {
26 base::DictionaryValue* dict_value;
27 if (!value->GetAsDictionary(&dict_value))
28 return false;
29 double x = 0;
30 double y = 0;
31 if (!dict_value->GetDouble("x", &x) ||
32 !dict_value->GetDouble("y", &y))
33 return false;
34 point->x = static_cast<int>(x);
35 point->y = static_cast<int>(y);
36 return true;
39 bool ParseFromValue(base::Value* value, WebSize* size) {
40 base::DictionaryValue* dict_value;
41 if (!value->GetAsDictionary(&dict_value))
42 return false;
43 double width = 0;
44 double height = 0;
45 if (!dict_value->GetDouble("width", &width) ||
46 !dict_value->GetDouble("height", &height))
47 return false;
48 size->width = static_cast<int>(width);
49 size->height = static_cast<int>(height);
50 return true;
53 bool ParseFromValue(base::Value* value, WebRect* rect) {
54 base::DictionaryValue* dict_value;
55 if (!value->GetAsDictionary(&dict_value))
56 return false;
57 double x = 0;
58 double y = 0;
59 double width = 0;
60 double height = 0;
61 if (!dict_value->GetDouble("left", &x) ||
62 !dict_value->GetDouble("top", &y) ||
63 !dict_value->GetDouble("width", &width) ||
64 !dict_value->GetDouble("height", &height))
65 return false;
66 rect->origin.x = static_cast<int>(x);
67 rect->origin.y = static_cast<int>(y);
68 rect->size.width = static_cast<int>(width);
69 rect->size.height = static_cast<int>(height);
70 return true;
73 base::Value* CreateValueFrom(const WebRect& rect) {
74 base::DictionaryValue* dict = new base::DictionaryValue();
75 dict->SetInteger("left", rect.X());
76 dict->SetInteger("top", rect.Y());
77 dict->SetInteger("width", rect.Width());
78 dict->SetInteger("height", rect.Height());
79 return dict;
82 Status CallAtomsJs(
83 const std::string& frame,
84 WebView* web_view,
85 const char* const* atom_function,
86 const base::ListValue& args,
87 scoped_ptr<base::Value>* result) {
88 return web_view->CallFunction(
89 frame, webdriver::atoms::asString(atom_function), args, result);
92 Status VerifyElementClickable(
93 const std::string& frame,
94 WebView* web_view,
95 const std::string& element_id,
96 const WebPoint& location) {
97 base::ListValue args;
98 args.Append(CreateElement(element_id));
99 args.Append(CreateValueFrom(location));
100 scoped_ptr<base::Value> result;
101 Status status = CallAtomsJs(
102 frame, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE,
103 args, &result);
104 if (status.IsError())
105 return status;
106 base::DictionaryValue* dict;
107 bool is_clickable = false;
108 if (!result->GetAsDictionary(&dict) ||
109 !dict->GetBoolean("clickable", &is_clickable)) {
110 return Status(kUnknownError,
111 "failed to parse value of IS_ELEMENT_CLICKABLE");
114 if (!is_clickable) {
115 std::string message;
116 if (!dict->GetString("message", &message))
117 message = "element is not clickable";
118 return Status(kUnknownError, message);
120 return Status(kOk);
123 Status ScrollElementRegionIntoViewHelper(
124 const std::string& frame,
125 WebView* web_view,
126 const std::string& element_id,
127 const WebRect& region,
128 bool center,
129 const std::string& clickable_element_id,
130 WebPoint* location) {
131 WebPoint tmp_location = *location;
132 base::ListValue args;
133 args.Append(CreateElement(element_id));
134 args.AppendBoolean(center);
135 args.Append(CreateValueFrom(region));
136 scoped_ptr<base::Value> result;
137 Status status = web_view->CallFunction(
138 frame, webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
139 args, &result);
140 if (status.IsError())
141 return status;
142 if (!ParseFromValue(result.get(), &tmp_location)) {
143 return Status(kUnknownError,
144 "failed to parse value of GET_LOCATION_IN_VIEW");
146 if (!clickable_element_id.empty()) {
147 WebPoint middle = tmp_location;
148 middle.Offset(region.Width() / 2, region.Height() / 2);
149 status = VerifyElementClickable(
150 frame, web_view, clickable_element_id, middle);
151 if (status.IsError())
152 return status;
154 *location = tmp_location;
155 return Status(kOk);
158 Status GetElementEffectiveStyle(
159 const std::string& frame,
160 WebView* web_view,
161 const std::string& element_id,
162 const std::string& property,
163 std::string* value) {
164 base::ListValue args;
165 args.Append(CreateElement(element_id));
166 args.AppendString(property);
167 scoped_ptr<base::Value> result;
168 Status status = web_view->CallFunction(
169 frame, webdriver::atoms::asString(webdriver::atoms::GET_EFFECTIVE_STYLE),
170 args, &result);
171 if (status.IsError())
172 return status;
173 if (!result->GetAsString(value)) {
174 return Status(kUnknownError,
175 "failed to parse value of GET_EFFECTIVE_STYLE");
177 return Status(kOk);
180 Status GetElementBorder(
181 const std::string& frame,
182 WebView* web_view,
183 const std::string& element_id,
184 int* border_left,
185 int* border_top) {
186 std::string border_left_str;
187 Status status = GetElementEffectiveStyle(
188 frame, web_view, element_id, "border-left-width", &border_left_str);
189 if (status.IsError())
190 return status;
191 std::string border_top_str;
192 status = GetElementEffectiveStyle(
193 frame, web_view, element_id, "border-top-width", &border_top_str);
194 if (status.IsError())
195 return status;
196 int border_left_tmp = -1;
197 int border_top_tmp = -1;
198 base::StringToInt(border_left_str, &border_left_tmp);
199 base::StringToInt(border_top_str, &border_top_tmp);
200 if (border_left_tmp == -1 || border_top_tmp == -1)
201 return Status(kUnknownError, "failed to get border width of element");
202 *border_left = border_left_tmp;
203 *border_top = border_top_tmp;
204 return Status(kOk);
207 } // namespace
209 base::DictionaryValue* CreateElement(const std::string& element_id) {
210 base::DictionaryValue* element = new base::DictionaryValue();
211 element->SetString(kElementKey, element_id);
212 return element;
215 base::Value* CreateValueFrom(const WebPoint& point) {
216 base::DictionaryValue* dict = new base::DictionaryValue();
217 dict->SetInteger("x", point.x);
218 dict->SetInteger("y", point.y);
219 return dict;
222 Status FindElement(
223 int interval_ms,
224 bool only_one,
225 const std::string* root_element_id,
226 Session* session,
227 WebView* web_view,
228 const base::DictionaryValue& params,
229 scoped_ptr<base::Value>* value) {
230 std::string strategy;
231 if (!params.GetString("using", &strategy))
232 return Status(kUnknownError, "'using' must be a string");
233 std::string target;
234 if (!params.GetString("value", &target))
235 return Status(kUnknownError, "'value' must be a string");
237 std::string script;
238 if (only_one)
239 script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT);
240 else
241 script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS);
242 scoped_ptr<base::DictionaryValue> locator(new base::DictionaryValue());
243 locator->SetString(strategy, target);
244 base::ListValue arguments;
245 arguments.Append(locator.release());
246 if (root_element_id)
247 arguments.Append(CreateElement(*root_element_id));
249 base::TimeTicks start_time = base::TimeTicks::Now();
250 while (true) {
251 scoped_ptr<base::Value> temp;
252 Status status = web_view->CallFunction(
253 session->GetCurrentFrameId(), script, arguments, &temp);
254 if (status.IsError())
255 return status;
257 if (!temp->IsType(base::Value::TYPE_NULL)) {
258 if (only_one) {
259 value->reset(temp.release());
260 return Status(kOk);
261 } else {
262 base::ListValue* result;
263 if (!temp->GetAsList(&result))
264 return Status(kUnknownError, "script returns unexpected result");
266 if (result->GetSize() > 0U) {
267 value->reset(temp.release());
268 return Status(kOk);
273 if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
274 if (only_one) {
275 return Status(kNoSuchElement, "Unable to locate element: {\"method\":\""
276 + strategy + "\",\"selector\":\"" + target + "\"}");
277 } else {
278 value->reset(new base::ListValue());
279 return Status(kOk);
282 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(interval_ms));
285 return Status(kUnknownError);
288 Status GetActiveElement(
289 Session* session,
290 WebView* web_view,
291 scoped_ptr<base::Value>* value) {
292 base::ListValue args;
293 return web_view->CallFunction(
294 session->GetCurrentFrameId(),
295 "function() { return document.activeElement || document.body }",
296 args,
297 value);
300 Status IsElementFocused(
301 Session* session,
302 WebView* web_view,
303 const std::string& element_id,
304 bool* is_focused) {
305 scoped_ptr<base::Value> result;
306 Status status = GetActiveElement(session, web_view, &result);
307 if (status.IsError())
308 return status;
309 scoped_ptr<base::Value> element_dict(CreateElement(element_id));
310 *is_focused = result->Equals(element_dict.get());
311 return Status(kOk);
314 Status GetElementAttribute(
315 Session* session,
316 WebView* web_view,
317 const std::string& element_id,
318 const std::string& attribute_name,
319 scoped_ptr<base::Value>* value) {
320 base::ListValue args;
321 args.Append(CreateElement(element_id));
322 args.AppendString(attribute_name);
323 return CallAtomsJs(
324 session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_ATTRIBUTE,
325 args, value);
328 Status IsElementAttributeEqualToIgnoreCase(
329 Session* session,
330 WebView* web_view,
331 const std::string& element_id,
332 const std::string& attribute_name,
333 const std::string& attribute_value,
334 bool* is_equal) {
335 scoped_ptr<base::Value> result;
336 Status status = GetElementAttribute(
337 session, web_view, element_id, attribute_name, &result);
338 if (status.IsError())
339 return status;
340 std::string actual_value;
341 if (result->GetAsString(&actual_value)) {
342 *is_equal =
343 base::LowerCaseEqualsASCII(actual_value, attribute_value.c_str());
344 } else {
345 *is_equal = false;
347 return status;
350 Status GetElementClickableLocation(
351 Session* session,
352 WebView* web_view,
353 const std::string& element_id,
354 WebPoint* location) {
355 std::string tag_name;
356 Status status = GetElementTagName(session, web_view, element_id, &tag_name);
357 if (status.IsError())
358 return status;
359 std::string target_element_id = element_id;
360 if (tag_name == "area") {
361 // Scroll the image into view instead of the area.
362 const char kGetImageElementForArea[] =
363 "function (element) {"
364 " var map = element.parentElement;"
365 " if (map.tagName.toLowerCase() != 'map')"
366 " throw new Error('the area is not within a map');"
367 " var mapName = map.getAttribute('name');"
368 " if (mapName == null)"
369 " throw new Error ('area\\'s parent map must have a name');"
370 " mapName = '#' + mapName.toLowerCase();"
371 " var images = document.getElementsByTagName('img');"
372 " for (var i = 0; i < images.length; i++) {"
373 " if (images[i].useMap.toLowerCase() == mapName)"
374 " return images[i];"
375 " }"
376 " throw new Error('no img is found for the area');"
377 "}";
378 base::ListValue args;
379 args.Append(CreateElement(element_id));
380 scoped_ptr<base::Value> result;
381 status = web_view->CallFunction(
382 session->GetCurrentFrameId(), kGetImageElementForArea, args, &result);
383 if (status.IsError())
384 return status;
385 const base::DictionaryValue* element_dict;
386 if (!result->GetAsDictionary(&element_dict) ||
387 !element_dict->GetString(kElementKey, &target_element_id))
388 return Status(kUnknownError, "no element reference returned by script");
390 bool is_displayed = false;
391 status = IsElementDisplayed(
392 session, web_view, target_element_id, true, &is_displayed);
393 if (status.IsError())
394 return status;
395 if (!is_displayed)
396 return Status(kElementNotVisible);
398 WebRect rect;
399 status = GetElementRegion(session, web_view, element_id, &rect);
400 if (status.IsError())
401 return status;
403 status = ScrollElementRegionIntoView(
404 session, web_view, target_element_id, rect,
405 true /* center */, element_id, location);
406 if (status.IsError())
407 return status;
408 location->Offset(rect.Width() / 2, rect.Height() / 2);
409 return Status(kOk);
412 Status GetElementEffectiveStyle(
413 Session* session,
414 WebView* web_view,
415 const std::string& element_id,
416 const std::string& property_name,
417 std::string* property_value) {
418 return GetElementEffectiveStyle(session->GetCurrentFrameId(), web_view,
419 element_id, property_name, property_value);
422 Status GetElementRegion(
423 Session* session,
424 WebView* web_view,
425 const std::string& element_id,
426 WebRect* rect) {
427 base::ListValue args;
428 args.Append(CreateElement(element_id));
429 scoped_ptr<base::Value> result;
430 Status status = web_view->CallFunction(
431 session->GetCurrentFrameId(), kGetElementRegionScript, args, &result);
432 if (status.IsError())
433 return status;
434 if (!ParseFromValue(result.get(), rect)) {
435 return Status(kUnknownError,
436 "failed to parse value of getElementRegion");
438 return Status(kOk);
441 Status GetElementTagName(
442 Session* session,
443 WebView* web_view,
444 const std::string& element_id,
445 std::string* name) {
446 base::ListValue args;
447 args.Append(CreateElement(element_id));
448 scoped_ptr<base::Value> result;
449 Status status = web_view->CallFunction(
450 session->GetCurrentFrameId(),
451 "function(elem) { return elem.tagName.toLowerCase(); }",
452 args, &result);
453 if (status.IsError())
454 return status;
455 if (!result->GetAsString(name))
456 return Status(kUnknownError, "failed to get element tag name");
457 return Status(kOk);
460 Status GetElementSize(
461 Session* session,
462 WebView* web_view,
463 const std::string& element_id,
464 WebSize* size) {
465 base::ListValue args;
466 args.Append(CreateElement(element_id));
467 scoped_ptr<base::Value> result;
468 Status status = CallAtomsJs(
469 session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_SIZE,
470 args, &result);
471 if (status.IsError())
472 return status;
473 if (!ParseFromValue(result.get(), size))
474 return Status(kUnknownError, "failed to parse value of GET_SIZE");
475 return Status(kOk);
478 Status IsElementDisplayed(
479 Session* session,
480 WebView* web_view,
481 const std::string& element_id,
482 bool ignore_opacity,
483 bool* is_displayed) {
484 base::ListValue args;
485 args.Append(CreateElement(element_id));
486 args.AppendBoolean(ignore_opacity);
487 scoped_ptr<base::Value> result;
488 Status status = CallAtomsJs(
489 session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_DISPLAYED,
490 args, &result);
491 if (status.IsError())
492 return status;
493 if (!result->GetAsBoolean(is_displayed))
494 return Status(kUnknownError, "IS_DISPLAYED should return a boolean value");
495 return Status(kOk);
498 Status IsElementEnabled(
499 Session* session,
500 WebView* web_view,
501 const std::string& element_id,
502 bool* is_enabled) {
503 base::ListValue args;
504 args.Append(CreateElement(element_id));
505 scoped_ptr<base::Value> result;
506 Status status = CallAtomsJs(
507 session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_ENABLED,
508 args, &result);
509 if (status.IsError())
510 return status;
511 if (!result->GetAsBoolean(is_enabled))
512 return Status(kUnknownError, "IS_ENABLED should return a boolean value");
513 return Status(kOk);
516 Status IsOptionElementSelected(
517 Session* session,
518 WebView* web_view,
519 const std::string& element_id,
520 bool* is_selected) {
521 base::ListValue args;
522 args.Append(CreateElement(element_id));
523 scoped_ptr<base::Value> result;
524 Status status = CallAtomsJs(
525 session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_SELECTED,
526 args, &result);
527 if (status.IsError())
528 return status;
529 if (!result->GetAsBoolean(is_selected))
530 return Status(kUnknownError, "IS_SELECTED should return a boolean value");
531 return Status(kOk);
534 Status IsOptionElementTogglable(
535 Session* session,
536 WebView* web_view,
537 const std::string& element_id,
538 bool* is_togglable) {
539 base::ListValue args;
540 args.Append(CreateElement(element_id));
541 scoped_ptr<base::Value> result;
542 Status status = web_view->CallFunction(
543 session->GetCurrentFrameId(), kIsOptionElementToggleableScript,
544 args, &result);
545 if (status.IsError())
546 return status;
547 if (!result->GetAsBoolean(is_togglable))
548 return Status(kUnknownError, "failed check if option togglable or not");
549 return Status(kOk);
552 Status SetOptionElementSelected(
553 Session* session,
554 WebView* web_view,
555 const std::string& element_id,
556 bool selected) {
557 // TODO(171034): need to fix throwing error if an alert is triggered.
558 base::ListValue args;
559 args.Append(CreateElement(element_id));
560 args.AppendBoolean(selected);
561 scoped_ptr<base::Value> result;
562 return CallAtomsJs(
563 session->GetCurrentFrameId(), web_view, webdriver::atoms::CLICK,
564 args, &result);
567 Status ToggleOptionElement(
568 Session* session,
569 WebView* web_view,
570 const std::string& element_id) {
571 bool is_selected;
572 Status status = IsOptionElementSelected(
573 session, web_view, element_id, &is_selected);
574 if (status.IsError())
575 return status;
576 return SetOptionElementSelected(session, web_view, element_id, !is_selected);
579 Status ScrollElementIntoView(
580 Session* session,
581 WebView* web_view,
582 const std::string& id,
583 const WebPoint* offset,
584 WebPoint* location) {
585 WebRect region;
586 Status status = GetElementRegion(session, web_view, id, &region);
587 if (status.IsError())
588 return status;
589 status = ScrollElementRegionIntoView(session, web_view, id, region,
590 false /* center */, std::string(), location);
591 if (status.IsError())
592 return status;
593 if (offset)
594 location->Offset(offset->x, offset->y);
595 else
596 location->Offset(region.size.width / 2, region.size.height / 2);
597 return Status(kOk);
600 Status ScrollElementRegionIntoView(
601 Session* session,
602 WebView* web_view,
603 const std::string& element_id,
604 const WebRect& region,
605 bool center,
606 const std::string& clickable_element_id,
607 WebPoint* location) {
608 WebPoint region_offset = region.origin;
609 WebSize region_size = region.size;
610 Status status = ScrollElementRegionIntoViewHelper(
611 session->GetCurrentFrameId(), web_view, element_id, region,
612 center, clickable_element_id, &region_offset);
613 if (status.IsError())
614 return status;
615 const char kFindSubFrameScript[] =
616 "function(xpath) {"
617 " return document.evaluate(xpath, document, null,"
618 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
619 "}";
620 for (std::list<FrameInfo>::reverse_iterator rit = session->frames.rbegin();
621 rit != session->frames.rend(); ++rit) {
622 base::ListValue args;
623 args.AppendString(
624 base::StringPrintf("//*[@cd_frame_id_ = '%s']",
625 rit->chromedriver_frame_id.c_str()));
626 scoped_ptr<base::Value> result;
627 status = web_view->CallFunction(
628 rit->parent_frame_id, kFindSubFrameScript, args, &result);
629 if (status.IsError())
630 return status;
631 const base::DictionaryValue* element_dict;
632 if (!result->GetAsDictionary(&element_dict))
633 return Status(kUnknownError, "no element reference returned by script");
634 std::string frame_element_id;
635 if (!element_dict->GetString(kElementKey, &frame_element_id))
636 return Status(kUnknownError, "failed to locate a sub frame");
638 // Modify |region_offset| by the frame's border.
639 int border_left = -1;
640 int border_top = -1;
641 status = GetElementBorder(
642 rit->parent_frame_id, web_view, frame_element_id,
643 &border_left, &border_top);
644 if (status.IsError())
645 return status;
646 region_offset.Offset(border_left, border_top);
648 status = ScrollElementRegionIntoViewHelper(
649 rit->parent_frame_id, web_view, frame_element_id,
650 WebRect(region_offset, region_size),
651 center, frame_element_id, &region_offset);
652 if (status.IsError())
653 return status;
655 *location = region_offset;
656 return Status(kOk);