From 77cdbc5fdd6e6e1818353f97c69870b2aafd2d25 Mon Sep 17 00:00:00 2001 From: Dounia Berrada Date: Mon, 11 Apr 2011 11:36:04 -0700 Subject: [PATCH] Adding capability to lookup many elements. Change-Id: I544497f010fdb723ef877e64caa15a0ed06c0fec --- core/java/android/webkit/webdriver/By.java | 43 +++++ .../android/webkit/webdriver/WebDriver.java | 51 ++++-- .../webkit/webdriver/WebDriverException.java | 38 ++++ .../android/webkit/webdriver/WebElement.java | 163 +++++++++++++++--- 4 files changed, 260 insertions(+), 35 deletions(-) create mode 100644 core/java/android/webkit/webdriver/WebDriverException.java diff --git a/core/java/android/webkit/webdriver/By.java b/core/java/android/webkit/webdriver/By.java index b40351db0213a..fa4fe74dc1582 100644 --- a/core/java/android/webkit/webdriver/By.java +++ b/core/java/android/webkit/webdriver/By.java @@ -16,12 +16,15 @@ package android.webkit.webdriver; +import java.util.List; + /** * Mechanism to locate elements within the DOM of the page. * @hide */ public abstract class By { public abstract WebElement findElement(WebElement element); + public abstract List findElements(WebElement element); /** * Locates an element by its HTML id attribute. @@ -37,6 +40,11 @@ public abstract class By { return element.findElementById(id); } + @Override + public List findElements(WebElement element) { + return element.findElementsById(id); // Yes, it happens a lot. + } + @Override public String toString() { return "By.id: " + id; @@ -59,6 +67,11 @@ public abstract class By { return element.findElementByLinkText(linkText); } + @Override + public List findElements(WebElement element) { + return element.findElementsByLinkText(linkText); + } + @Override public String toString() { return "By.linkText: " + linkText; @@ -83,6 +96,11 @@ public abstract class By { return element.findElementByPartialLinkText(linkText); } + @Override + public List findElements(WebElement element) { + return element.findElementsByPartialLinkText(linkText); + } + @Override public String toString() { return "By.partialLinkText: " + linkText; @@ -104,6 +122,11 @@ public abstract class By { return element.findElementByName(name); } + @Override + public List findElements(WebElement element) { + return element.findElementsByName(name); + } + @Override public String toString() { return "By.name: " + name; @@ -124,6 +147,11 @@ public abstract class By { return element.findElementByClassName(className); } + @Override + public List findElements(WebElement element) { + return element.findElementsByClassName(className); + } + @Override public String toString() { return "By.className: " + className; @@ -145,6 +173,11 @@ public abstract class By { return element.findElementByCss(css); } + @Override + public List findElements(WebElement element) { + return element.findElementsByCss(css); + } + @Override public String toString() { return "By.css: " + css; @@ -167,6 +200,11 @@ public abstract class By { return element.findElementByTagName(tagName); } + @Override + public List findElements(WebElement element) { + return element.findElementsByTagName(tagName); + } + @Override public String toString() { return "By.tagName: " + tagName; @@ -193,6 +231,11 @@ public abstract class By { return element.findElementByXPath(xpath); } + @Override + public List findElements(WebElement element) { + return element.findElementsByXPath(xpath); + } + @Override public String toString() { return "By.xpath: " + xpath; diff --git a/core/java/android/webkit/webdriver/WebDriver.java b/core/java/android/webkit/webdriver/WebDriver.java index 90e701f99b477..618820db21df9 100644 --- a/core/java/android/webkit/webdriver/WebDriver.java +++ b/core/java/android/webkit/webdriver/WebDriver.java @@ -16,19 +16,19 @@ package android.webkit.webdriver; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; +import android.os.Handler; +import android.os.Message; +import android.webkit.WebView; import com.android.internal.R; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import android.os.Handler; -import android.os.Message; -import android.webkit.WebView; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -190,7 +190,6 @@ public class WebDriver { WebchromeClientWrapper chromeWrapper = new WebchromeClientWrapper( webview.getWebChromeClient(), this); mWebView.setWebChromeClient(chromeWrapper); - mDocumentElement = new WebElement(this, ""); mWebView.addJavascriptInterface(new JavascriptResultReady(), "webdriver"); } @@ -203,6 +202,7 @@ public class WebDriver { */ public void get(String url) { executeCommand(CMD_GET_URL, url, LOADING_TIMEOUT); + mDocumentElement = (WebElement) executeScript("return document.body;"); } /** @@ -223,9 +223,25 @@ public class WebDriver { * no matching element was found. */ public WebElement findElement(By by) { + checkNotNull(mDocumentElement, "Load a page using WebDriver.get() " + + "before looking for elements."); return by.findElement(mDocumentElement); } + /** + * Finds all {@link android.webkit.webdriver.WebElement} within the page + * using the given method. + * + * @param by The locating mechanism to use. + * @return A list of all {@link android.webkit.webdriver.WebElement} found, + * or an empty list if nothing matches. + */ + public List findElements(By by) { + checkNotNull(mDocumentElement, "Load a page using WebDriver.get() " + + "before looking for elements."); + return by.findElements(mDocumentElement); + } + /** * Clears the WebView. */ @@ -300,8 +316,10 @@ public class WebDriver { toReturn.append(toAdd.substring(0, toAdd.length() -1) + "}"); } else if (args[i] instanceof WebElement) { // WebElement are represented in JavaScript by Objects as - // follow: {ELEMENT:"id"} - toReturn.append("{" + ELEMENT_KEY + ":\"" + // follow: {ELEMENT:"id"} where "id" refers to the id + // of the HTML element in the javascript cache that can + // be accessed throught bot.inject.cache.getCache_() + toReturn.append("{\"" + ELEMENT_KEY + "\":\"" + ((WebElement) args[i]).getId() + "\"}"); } else if (args[i] instanceof Number || args[i] instanceof Boolean) { toReturn.append(String.valueOf(args[i])); @@ -310,9 +328,10 @@ public class WebDriver { } else { throw new IllegalArgumentException( "Javascript arguments can be " - + "a Number, a Boolean, a String, a WebElement, " - + "or a List or a Map of those. Got: " - + ((args[i] == null) ? "null" : args[i].toString())); + + "a Number, a Boolean, a String, a WebElement, " + + "or a List or a Map of those. Got: " + + ((args[i] == null) ? "null" : args[i].getClass() + + ", value: " + args[i].toString())); } } return toReturn.toString(); @@ -457,7 +476,7 @@ public class WebDriver { case STALE_ELEMENT_REFERENCE: throw new WebElementStaleException("WebElement is stale."); default: - throw new RuntimeException("Error: " + errorMsg); + throw new WebDriverException("Error: " + errorMsg); } } @@ -523,4 +542,10 @@ public class WebDriver { } return mJsResult; } + + private void checkNotNull(Object obj, String errosMsg) { + if (obj == null) { + throw new NullPointerException(errosMsg); + } + } } diff --git a/core/java/android/webkit/webdriver/WebDriverException.java b/core/java/android/webkit/webdriver/WebDriverException.java new file mode 100644 index 0000000000000..1a579c2974812 --- /dev/null +++ b/core/java/android/webkit/webdriver/WebDriverException.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit.webdriver; + +/** + * @hide + */ +public class WebDriverException extends RuntimeException { + public WebDriverException() { + super(); + } + + public WebDriverException(String reason) { + super(reason); + } + + public WebDriverException(String reason, Throwable cause) { + super(reason, cause); + } + + public WebDriverException(Throwable cause) { + super(cause); + } +} diff --git a/core/java/android/webkit/webdriver/WebElement.java b/core/java/android/webkit/webdriver/WebElement.java index 384d55f4b552e..96700d77f4828 100644 --- a/core/java/android/webkit/webdriver/WebElement.java +++ b/core/java/android/webkit/webdriver/WebElement.java @@ -18,6 +18,8 @@ package android.webkit.webdriver; import com.android.internal.R; +import java.util.List; + /** * Represents an HTML element. Typically most interactions with a web page * will be performed through this class. @@ -28,6 +30,15 @@ public class WebElement { private final String mId; private final WebDriver mDriver; + private static final String LOCATOR_ID = "id"; + private static final String LOCATOR_LINK_TEXT = "linkText"; + private static final String LOCATOR_PARTIAL_LINK_TEXT = "partialLinkText"; + private static final String LOCATOR_NAME = "name"; + private static final String LOCATOR_CLASS_NAME = "className"; + private static final String LOCATOR_CSS = "css"; + private static final String LOCATOR_TAG_NAME = "tagName"; + private static final String LOCATOR_XPATH = "xpath"; + /** * Package constructor to prevent clients from creating a new WebElement * instance. @@ -37,11 +48,10 @@ public class WebElement { * that can be accessed through JavaScript using "bot.inject.cache". * * @param driver The WebDriver instance to use. - * @param id The index of the HTML element in the JavaSctipt cache. Pass - * an empty String to indicate that this is the + * @param id The index of the HTML element in the JavaSctipt cache. * document.documentElement object. */ - /* Package */ WebElement(final WebDriver driver, final String id) { + /* package */ WebElement(final WebDriver driver, final String id) { this.mId = id; this.mDriver = driver; } @@ -57,6 +67,18 @@ public class WebElement { return by.findElement(this); } + /** + * Finds all {@link android.webkit.webdriver.WebElement} within the page + * using the given method. + * + * @param by The locating mechanism to use. + * @return A list of all {@link android.webkit.webdriver.WebElement} found, + * or an empty list if nothing matches. + */ + public List findElements(final By by) { + return by.findElements(this); + } + /** * Gets the visisble (i.e. not hidden by CSS) innerText of this element, * inlcuding sub-elements. @@ -67,47 +89,143 @@ public class WebElement { */ public String getText() { String getText = mDriver.getResourceAsString(R.raw.get_text_android); - if (mId.equals("")) { - return null; - } return (String) executeAtom(getText, this); } + /** + * Gets the value of an HTML attribute for this element or the value of the + * property with the same name if the attribute is not present. If neither + * is set, null is returned. + * + * @param attribute the HTML attribute. + * @return the value of that attribute or the value of the property with the + * same name if the attribute is not set, or null if neither are set. For + * boolean attribute values this will return the string "true" or "false". + */ + public String getAttribute(String attribute) { + String getAttribute = mDriver.getResourceAsString( + R.raw.get_attribute_value_android); + return (String) executeAtom(getAttribute, this, attribute); + } + + /** + * @return the tag name of this element. + */ + public String getTagName() { + return (String) mDriver.executeScript("return arguments[0].tagName;", + this); + } + + /** + * @return true if this element is enabled, false otherwise. + */ + public boolean isEnabled() { + String isEnabled = mDriver.getResourceAsString( + R.raw.is_enabled_android); + return (Boolean) executeAtom(isEnabled, this); + } + + /** + * Determines whether this element is selected or not. This applies to input + * elements such as checkboxes, options in a select, and radio buttons. + * + * @return True if this element is selected, false otherwise. + */ + public boolean isSelected() { + String isSelected = mDriver.getResourceAsString( + R.raw.is_selected_android); + return (Boolean) executeAtom(isSelected, this); + } + + /** + * Selects an element on the page. This works for selecting checkboxes, + * options in a select, and radio buttons. + */ + public void setSelected() { + String setSelected = mDriver.getResourceAsString( + R.raw.set_selected_android); + executeAtom(setSelected, this); + } + + /** + * This toggles the checkboxe state from selected to not selected, or + * from not selected to selected. + * + * @return True if the toggled element is selected, false otherwise. + */ + public boolean toggle() { + String toggle = mDriver.getResourceAsString(R.raw.toggle_android); + return (Boolean) executeAtom(toggle, this); + } + /*package*/ String getId() { return mId; } /* package */ WebElement findElementById(final String locator) { - return findElement("id", locator); + return findElement(LOCATOR_ID, locator); } /* package */ WebElement findElementByLinkText(final String linkText) { - return findElement("linkText", linkText); + return findElement(LOCATOR_LINK_TEXT, linkText); } /* package */ WebElement findElementByPartialLinkText( final String linkText) { - return findElement("partialLinkText", linkText); + return findElement(LOCATOR_PARTIAL_LINK_TEXT, linkText); } /* package */ WebElement findElementByName(final String name) { - return findElement("name", name); + return findElement(LOCATOR_NAME, name); } /* package */ WebElement findElementByClassName(final String className) { - return findElement("className", className); + return findElement(LOCATOR_CLASS_NAME, className); } /* package */ WebElement findElementByCss(final String css) { - return findElement("css", css); + return findElement(LOCATOR_CSS, css); } /* package */ WebElement findElementByTagName(final String tagName) { - return findElement("tagName", tagName); + return findElement(LOCATOR_TAG_NAME, tagName); } /* package */ WebElement findElementByXPath(final String xpath) { - return findElement("xpath", xpath); + return findElement(LOCATOR_XPATH, xpath); + } + + /* package */ List findElementsById(final String locator) { + return findElements(LOCATOR_ID, locator); + } + + /* package */ List findElementsByLinkText(final String linkText) { + return findElements(LOCATOR_LINK_TEXT, linkText); + } + + /* package */ List findElementsByPartialLinkText( + final String linkText) { + return findElements(LOCATOR_PARTIAL_LINK_TEXT, linkText); + } + + /* package */ List findElementsByName(final String name) { + return findElements(LOCATOR_NAME, name); + } + + /* package */ List findElementsByClassName(final String className) { + return findElements(LOCATOR_CLASS_NAME, className); + } + + /* package */ List findElementsByCss(final String css) { + return findElements(LOCATOR_CSS, css); + } + + /* package */ List findElementsByTagName(final String tagName) { + return findElements(LOCATOR_TAG_NAME, tagName); + } + + /* package */ List findElementsByXPath(final String xpath) { + return findElements(LOCATOR_XPATH, xpath); } private Object executeAtom(final String atom, final Object... args) { @@ -116,17 +234,18 @@ public class WebElement { atom + ")(" + scriptArgs + ")"); } + private List findElements(String strategy, String locator) { + String findElements = mDriver.getResourceAsString( + R.raw.find_elements_android); + return (List) executeAtom(findElements, + strategy, locator, this); + } + private WebElement findElement(String strategy, String locator) { String findElement = mDriver.getResourceAsString( R.raw.find_element_android); - WebElement el; - if (mId.equals("")) { - // Use default as root which is the document object - el = (WebElement) executeAtom(findElement, strategy, locator); - } else { - // Use this as root - el = (WebElement) executeAtom(findElement, strategy, locator, this); - } + WebElement el = (WebElement) executeAtom(findElement, + strategy, locator, this); if (el == null) { throw new WebElementNotFoundException("Could not find element " + "with " + strategy + ": " + locator);