Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5201)

Unified Diff: chrome/test/chromedriver/element_util.cc

Issue 12764021: [chromedriver] Support clicking an element in sub frames. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/test/chromedriver/element_util.cc
diff --git a/chrome/test/chromedriver/element_util.cc b/chrome/test/chromedriver/element_util.cc
index 4b1a361af6d5815ae4834659bd6d08c47e20d408..10a3db254e400af2a104c09dcb3f761c958ca9b3 100644
--- a/chrome/test/chromedriver/element_util.cc
+++ b/chrome/test/chromedriver/element_util.cc
@@ -5,6 +5,8 @@
#include "chrome/test/chromedriver/element_util.h"
#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/strings/string_number_conversions.cc"
#include "base/threading/platform_thread.h"
#include "base/time.h"
#include "base/values.h"
@@ -86,14 +88,111 @@ base::Value* CreateValueFrom(const WebRect* rect) {
}
Status CallAtomsJs(
- Session* session,
+ const std::string& frame,
WebView* web_view,
const char* const* atom_function,
const base::ListValue& args,
scoped_ptr<base::Value>* result) {
return web_view->CallFunction(
- session->frame, webdriver::atoms::asString(atom_function),
- args, result);
+ frame, webdriver::atoms::asString(atom_function), args, result);
+}
+
+Status VerifyElementClickable(
+ const std::string& frame,
+ WebView* web_view,
+ const std::string& element_id,
+ WebPoint* location) {
+ base::ListValue args;
+ args.Append(CreateElement(element_id));
+ args.Append(CreateValueFrom(location));
+ scoped_ptr<base::Value> result;
+ Status status = CallAtomsJs(
+ frame, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE,
+ args, &result);
+ if (status.IsError())
+ return status;
+ base::DictionaryValue* dict;
+ bool is_clickable;
+ if (!result->GetAsDictionary(&dict) ||
+ !dict->GetBoolean("clickable", &is_clickable))
+ return Status(kUnknownError, "fail to parse value of IS_ELEMENT_CLICKABLE");
+
+ if (!is_clickable) {
+ std::string message;
+ if (!dict->GetString("message", &message))
+ message = "element is not clickable";
+ return Status(kUnknownError, message);
+ }
+ return Status(kOk);
+}
+
+Status ScrollElementRegionIntoViewHelper(
+ const std::string& frame,
+ WebView* web_view,
+ const std::string& element_id,
+ const WebRect& region,
+ bool center,
+ bool verify_clickable,
+ WebPoint* location) {
+ base::ListValue args;
+ args.Append(CreateElement(element_id));
+ args.AppendBoolean(center);
+ args.Append(CreateValueFrom(&region));
+ scoped_ptr<base::Value> result;
+ Status status = web_view->CallFunction(
+ frame, webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
+ args, &result);
+ if (status.IsError())
+ return status;
+ if (!ParseFromValue(result.get(), location))
+ return Status(kUnknownError, "fail to parse value of GET_LOCATION_IN_VIEW");
+ if (verify_clickable) {
+ WebPoint middle = *location;
+ middle.Offset(region.width() / 2, region.height() / 2);
+ return VerifyElementClickable(frame, web_view, element_id, &middle);
+ }
+ return Status(kOk);
+}
+
+Status GetElementEffectiveStyle(
+ const std::string& frame,
+ WebView* web_view,
+ const std::string& element_id,
+ const std::string& property,
+ std::string* value) {
+ base::ListValue args;
+ args.Append(CreateElement(element_id));
+ args.AppendString(property);
+ scoped_ptr<base::Value> result;
+ Status status = web_view->CallFunction(
+ frame, webdriver::atoms::asString(webdriver::atoms::GET_EFFECTIVE_STYLE),
+ args, &result);
+ if (status.IsError())
+ return status;
+ if (!result->GetAsString(value))
+ return Status(kUnknownError, "fail to parse value of GET_EFFECTIVE_STYLE");
+ return Status(kOk);
+}
+
+Status GetElementBorder(
+ const std::string& frame,
+ WebView* web_view,
+ const std::string& element_id,
+ int* border_left,
+ int* border_top) {
+ std::string border_left_str;
+ Status status = GetElementEffectiveStyle(
+ frame, web_view, element_id, "border-left-width", &border_left_str);
+ if (status.IsError())
+ return status;
+ std::string border_top_str;
+ status = GetElementEffectiveStyle(
+ frame, web_view, element_id, "border-top-width", &border_top_str);
+ if (status.IsError())
+ return status;
+ base::StringToInt(border_left_str, border_left);
kkania 2013/03/12 04:04:07 check return value?
chrisgao (Use stgao instead) 2013/03/12 17:41:18 Done.
+ base::StringToInt(border_top_str, border_top);
+ return Status(kOk);
}
} // namespace
@@ -180,7 +279,7 @@ Status GetElementAttribute(
args.Append(CreateElement(element_id));
args.AppendString(attribute_name);
return CallAtomsJs(
- session, web_view, webdriver::atoms::GET_ATTRIBUTE, args, value);
+ session->frame, web_view, webdriver::atoms::GET_ATTRIBUTE, args, value);
}
Status IsElementAttributeEqualToIgnoreCase(
@@ -207,8 +306,7 @@ Status GetElementClickableLocation(
Session* session,
WebView* web_view,
const std::string& element_id,
- WebPoint* location,
- bool* is_clickable) {
+ WebPoint* location) {
bool is_displayed = false;
Status status = IsElementDisplayed(
session, web_view, element_id, true, &is_displayed);
@@ -223,14 +321,11 @@ Status GetElementClickableLocation(
return status;
status = ScrollElementRegionIntoView(
- session, web_view, element_id, rect, true, location);
+ session, web_view, element_id, rect,
+ true /* center */, true /* verify_clickable */, location);
if (status.IsError())
return status;
- location->offset(rect.width() / 2, rect.height() / 2);
- if (is_clickable) {
- return IsElementClickable(
- session, web_view, element_id, location, is_clickable);
- }
+ location->Offset(rect.width() / 2, rect.height() / 2);
return Status(kOk);
}
@@ -280,7 +375,7 @@ Status GetElementSize(
args.Append(CreateElement(element_id));
scoped_ptr<base::Value> result;
Status status = CallAtomsJs(
- session, web_view, webdriver::atoms::GET_SIZE, args, &result);
+ session->frame, web_view, webdriver::atoms::GET_SIZE, args, &result);
if (status.IsError())
return status;
if (!ParseFromValue(result.get(), size))
@@ -288,34 +383,6 @@ Status GetElementSize(
return Status(kOk);
}
-Status IsElementClickable(
- Session* session,
- WebView* web_view,
- const std::string& element_id,
- WebPoint* location,
- bool* is_clickable) {
- base::ListValue args;
- args.Append(CreateElement(element_id));
- args.Append(CreateValueFrom(location));
- scoped_ptr<base::Value> result;
- Status status = CallAtomsJs(
- session, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE, args, &result);
- if (status.IsError())
- return status;
- base::DictionaryValue* dict;
- if (!result->GetAsDictionary(&dict) ||
- !dict->GetBoolean("clickable", is_clickable))
- return Status(kUnknownError, "fail to parse value of IS_ELEMENT_CLICKABLE");
-
- if (!*is_clickable) {
- std::string message;
- if (!dict->GetString("message", &message))
- message = "element is not clickable";
- return Status(kOk, message);
- }
- return Status(kOk);
-}
-
Status IsElementDisplayed(
Session* session,
WebView* web_view,
@@ -327,7 +394,7 @@ Status IsElementDisplayed(
args.AppendBoolean(ignore_opacity);
scoped_ptr<base::Value> result;
Status status = CallAtomsJs(
- session, web_view, webdriver::atoms::IS_DISPLAYED, args, &result);
+ session->frame, web_view, webdriver::atoms::IS_DISPLAYED, args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_displayed))
@@ -344,7 +411,7 @@ Status IsElementEnabled(
args.Append(CreateElement(element_id));
scoped_ptr<base::Value> result;
Status status = CallAtomsJs(
- session, web_view, webdriver::atoms::IS_ENABLED, args, &result);
+ session->frame, web_view, webdriver::atoms::IS_ENABLED, args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_enabled))
@@ -361,7 +428,7 @@ Status IsOptionElementSelected(
args.Append(CreateElement(element_id));
scoped_ptr<base::Value> result;
Status status = CallAtomsJs(
- session, web_view, webdriver::atoms::IS_SELECTED, args, &result);
+ session->frame, web_view, webdriver::atoms::IS_SELECTED, args, &result);
if (status.IsError())
return status;
if (!result->GetAsBoolean(is_selected))
@@ -397,7 +464,7 @@ Status SetOptionElementSelected(
args.AppendBoolean(selected);
scoped_ptr<base::Value> result;
return CallAtomsJs(
- session, web_view, webdriver::atoms::CLICK, args, &result);
+ session->frame, web_view, webdriver::atoms::CLICK, args, &result);
}
Status ToggleOptionElement(
@@ -422,7 +489,8 @@ Status ScrollElementIntoView(
if (status.IsError())
return status;
return ScrollElementRegionIntoView(
- session, web_view, id, WebRect(WebPoint(0, 0), size), false, location);
+ session, web_view, id, WebRect(WebPoint(0, 0), size),
+ false /* center */, false /* verify_clickable */, location);
}
Status ScrollElementRegionIntoView(
@@ -431,21 +499,72 @@ Status ScrollElementRegionIntoView(
const std::string& element_id,
const WebRect& region,
bool center,
+ bool verify_clickable,
WebPoint* location) {
WebPoint region_offset = region.origin;
- base::ListValue args;
- args.Append(CreateElement(element_id));
- args.AppendBoolean(center);
- args.Append(CreateValueFrom(&region));
- scoped_ptr<base::Value> result;
-
- // TODO(chrisgao): Nested frame. See http://crbug.com/170998.
- Status status = CallAtomsJs(
- session, web_view, webdriver::atoms::GET_LOCATION_IN_VIEW, args, &result);
+ WebSize region_size = region.size;
+ Status status = ScrollElementRegionIntoViewHelper(
+ session->frame, web_view, element_id, region,
+ center, verify_clickable, &region_offset);
if (status.IsError())
return status;
- if (!ParseFromValue(result.get(), &region_offset))
- return Status(kUnknownError, "fail to parse value of GET_LOCATION_IN_VIEW");
+ if (!session->frame.empty()) {
+ std::list<Frame> frames;
+ status = web_view->GetFramePath(session->frame, &frames);
+ if (status.IsError())
+ return status;
+ std::string root_frame_id = frames.back().id;
+ const char* kFindSubFrameScript =
+ "function(xpath) {"
+ " return document.evaluate(xpath, document, null,"
+ " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
+ "}";
+ for (std::list<Frame>::const_iterator it = frames.begin();
+ it != frames.end(); ++it) {
+ if (!it->IsSubFrame())
+ break;
+
+ std::string xpath = "(/html/body//iframe|/html/frameset/frame)";
+ if (it->HasName())
+ xpath += base::StringPrintf("[@name=\"%s\"]", it->name.c_str());
kkania 2013/03/12 04:04:07 why do we support both name and index? i don't th
chrisgao (Use stgao instead) 2013/03/12 17:41:18 Because name of frame is optional in the Inspector
+ else
+ xpath += base::StringPrintf("[%d]", it->index + 1);
+
+ std::string parent_frame_id = it->parent_id;
+ if (parent_frame_id == root_frame_id)
+ parent_frame_id = "";
+
+ base::ListValue args;
+ args.AppendString(xpath);
+ scoped_ptr<base::Value> result;
+ status = web_view->CallFunction(
+ parent_frame_id, kFindSubFrameScript, args, &result);
+ if (status.IsError())
+ return status;
+ const base::DictionaryValue* element_dict;
+ if (!result->GetAsDictionary(&element_dict))
+ return Status(kUnknownError, "no element reference returned by script");
+ std::string frame_element_id;
+ if (!element_dict->GetString(kElementKey, &frame_element_id))
+ return Status(kUnknownError, "fail to locate a sub frame");
+
+ // Modify |region_offset| by the frame's border.
+ int border_lef, border_top;
+ status = GetElementBorder(
+ parent_frame_id, web_view, frame_element_id,
+ &border_lef, &border_top);
+ if (status.IsError())
+ return status;
+ region_offset.Offset(border_lef, border_top);
+
+ status = ScrollElementRegionIntoViewHelper(
+ parent_frame_id, web_view, frame_element_id,
+ WebRect(region_offset, region_size),
+ center, verify_clickable, &region_offset);
+ if (status.IsError())
+ return status;
+ }
+ }
*location = region_offset;
return Status(kOk);
}

Powered by Google App Engine
This is Rietveld 408576698