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