org.openqa.selenium.htmlunit.HtmlUnitWebElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit-driver Show documentation
Show all versions of htmlunit-driver Show documentation
WebDriver compatible driver for HtmlUnit headless browser
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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 org.openqa.selenium.htmlunit;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.ElementNotInteractableException;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.InvalidSelectorException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.internal.Coordinates;
import org.openqa.selenium.internal.FindsByCssSelector;
import org.openqa.selenium.internal.FindsById;
import org.openqa.selenium.internal.FindsByLinkText;
import org.openqa.selenium.internal.FindsByTagName;
import org.openqa.selenium.internal.FindsByXPath;
import org.openqa.selenium.interactions.internal.Locatable;
import org.openqa.selenium.WrapsDriver;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.support.Color;
import org.openqa.selenium.support.Colors;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlImageInput;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLInputElement;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
public class HtmlUnitWebElement implements WrapsDriver,
FindsById, FindsByLinkText, FindsByXPath, FindsByTagName,
FindsByCssSelector, Locatable, WebElement, Coordinates {
protected final HtmlUnitDriver parent;
protected final int id;
protected final DomElement element;
private static final String[] booleanAttributes = {
"async",
"autofocus",
"autoplay",
"checked",
"compact",
"complete",
"controls",
"declare",
"defaultchecked",
"defaultselected",
"defer",
"disabled",
"draggable",
"ended",
"formnovalidate",
"hidden",
"indeterminate",
"iscontenteditable",
"ismap",
"itemscope",
"loop",
"multiple",
"muted",
"nohref",
"noresize",
"noshade",
"novalidate",
"nowrap",
"open",
"paused",
"pubdate",
"readonly",
"required",
"reversed",
"scoped",
"seamless",
"seeking",
"selected",
"spellcheck",
"truespeed",
"willvalidate"
};
private String toString;
public HtmlUnitWebElement(HtmlUnitDriver parent, int id, DomElement element) {
this.parent = parent;
this.id = id;
this.element = element;
}
@Override
public void click() {
verifyCanInteractWithElement(true);
parent.click(element, true);
}
@Override
public void submit() {
parent.submit(this);
}
void submitImpl() {
try {
if (element instanceof HtmlForm) {
submitForm((HtmlForm) element);
} else if ((element instanceof HtmlSubmitInput) || (element instanceof HtmlImageInput)) {
element.click();
} else if (element instanceof HtmlInput) {
HtmlForm form = ((HtmlElement) element).getEnclosingForm();
if (form == null) {
throw new NoSuchElementException("Unable to find the containing form");
}
submitForm(form);
} else {
HtmlUnitWebElement form = findParentForm();
if (form == null) {
throw new NoSuchElementException("Unable to find the containing form");
}
form.submitImpl();
}
} catch (IOException e) {
throw new WebDriverException(e);
}
}
private void submitForm(HtmlForm form) {
assertElementNotStale();
List allElements = new ArrayList<>();
allElements.addAll(form.getElementsByTagName("input"));
allElements.addAll(form.getElementsByTagName("button"));
HtmlElement submit = null;
for (HtmlElement e : allElements) {
if (!isSubmitElement(e)) {
continue;
}
if (submit == null) {
submit = e;
}
}
if (submit == null) {
if (parent.isJavascriptEnabled()) {
ScriptResult eventResult = form.fireEvent("submit");
if (!ScriptResult.isFalse(eventResult)) {
parent.executeScript("arguments[0].submit()", form);
}
return;
}
throw new WebDriverException("Cannot locate element used to submit form");
}
try {
// this has to ignore the visibility like browsers are doing
submit.click(false, false, false, true, true, false);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
private static boolean isSubmitElement(HtmlElement element) {
HtmlElement candidate = null;
if (element instanceof HtmlSubmitInput && !((HtmlSubmitInput) element).isDisabled()) {
candidate = element;
} else if (element instanceof HtmlImageInput && !((HtmlImageInput) element).isDisabled()) {
candidate = element;
} else if (element instanceof HtmlButton) {
HtmlButton button = (HtmlButton) element;
if ("submit".equalsIgnoreCase(button.getTypeAttribute()) && !button.isDisabled()) {
candidate = element;
}
}
return candidate != null;
}
@Override
public void clear() {
assertElementNotStale();
if (element instanceof HtmlInput) {
HtmlInput htmlInput = (HtmlInput) element;
if (htmlInput.isReadOnly()) {
throw new InvalidElementStateException("You may only edit editable elements");
}
if (htmlInput.isDisabled()) {
throw new InvalidElementStateException("You may only interact with enabled elements");
}
htmlInput.setValueAttribute("");
htmlInput.fireEvent("change");
} else if (element instanceof HtmlTextArea) {
HtmlTextArea htmlTextArea = (HtmlTextArea) element;
if (htmlTextArea.isReadOnly()) {
throw new InvalidElementStateException("You may only edit editable elements");
}
if (htmlTextArea.isDisabled()) {
throw new InvalidElementStateException("You may only interact with enabled elements");
}
htmlTextArea.setText("");
} else if (!element.getAttribute("contenteditable").equals(DomElement.ATTRIBUTE_NOT_DEFINED)) {
element.setTextContent("");
}
}
void verifyCanInteractWithElement(boolean ignoreDisabled) {
assertElementNotStale();
Boolean displayed = parent.implicitlyWaitFor(new Callable() {
@Override
public Boolean call() throws Exception {
return isDisplayed();
}
});
if (displayed == null || !displayed) {
throw new ElementNotInteractableException("You may only interact with visible elements");
}
if (!ignoreDisabled && !isEnabled()) {
throw new InvalidElementStateException("You may only interact with enabled elements");
}
}
void switchFocusToThisIfNeeded() {
HtmlUnitWebElement oldActiveElement =
((HtmlUnitWebElement) parent.switchTo().activeElement());
boolean jsEnabled = parent.isJavascriptEnabled();
boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
try {
boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
if (jsEnabled &&
!oldActiveEqualsCurrent &&
!isBody) {
oldActiveElement.element.blur();
}
} catch (StaleElementReferenceException ex) {
// old element has gone, do nothing
}
element.focus();
}
@Override
public void sendKeys(CharSequence... value) {
if (value == null) {
throw new IllegalArgumentException("Keys to send should nor be null");
}
parent.sendKeys(this, value);
}
@Override
public String getTagName() {
assertElementNotStale();
return element.getNodeName();
}
@Override
public String getAttribute(String name) {
assertElementNotStale();
final String lowerName = name.toLowerCase();
String value = element.getAttribute(name);
if (element instanceof HtmlInput &&
("selected".equals(lowerName) || "checked".equals(lowerName))) {
return trueOrNull(((HtmlInput) element).isChecked());
}
if ("href".equals(lowerName) || "src".equals(lowerName)) {
if (!element.hasAttribute(name)) {
return null;
}
String link = element.getAttribute(name).trim();
HtmlPage page = (HtmlPage) element.getPage();
try {
return page.getFullyQualifiedUrl(link).toString();
} catch (MalformedURLException e) {
return null;
}
}
if ("disabled".equals(lowerName)) {
return trueOrNull(!isEnabled());
}
if ("multiple".equals(lowerName) && element instanceof HtmlSelect) {
String multipleAttribute = ((HtmlSelect) element).getMultipleAttribute();
if ("".equals(multipleAttribute)) {
return trueOrNull(element.hasAttribute("multiple"));
}
return "true";
}
for (String booleanAttribute : booleanAttributes) {
if (booleanAttribute.equals(lowerName)) {
return trueOrNull(element.hasAttribute(lowerName));
}
}
if ("index".equals(lowerName) && element instanceof HtmlOption) {
HtmlSelect select = ((HtmlOption) element).getEnclosingSelect();
List allOptions = select.getOptions();
for (int i = 0; i < allOptions.size(); i++) {
HtmlOption option = select.getOption(i);
if (element.equals(option)) {
return String.valueOf(i);
}
}
return null;
}
if ("value".equals(lowerName)) {
if (element instanceof HtmlFileInput) {
return ((HTMLInputElement) element.getScriptableObject()).getValue();
}
if (element instanceof HtmlTextArea) {
return ((HtmlTextArea) element).getText();
}
// According to
// http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-value-OPTION
// if the value attribute doesn't exist, getting the "value" attribute defers to the
// option's content.
if (element instanceof HtmlOption && !element.hasAttribute("value")) {
return element.getTextContent();
}
return value == null ? "" : value;
}
if (!value.isEmpty()) {
return value;
}
if (element.hasAttribute(name)) {
return "";
}
final Object scriptable = element.getScriptableObject();
if (scriptable instanceof Scriptable) {
final Object slotVal = ScriptableObject.getProperty((Scriptable) scriptable, name);
if (slotVal instanceof String) {
return (String) slotVal;
}
}
return null;
}
private static String trueOrNull(boolean condition) {
return condition ? "true" : null;
}
@Override
public boolean isSelected() {
assertElementNotStale();
if (element instanceof HtmlInput) {
return ((HtmlInput) element).isChecked();
} else if (element instanceof HtmlOption) {
return ((HtmlOption) element).isSelected();
}
throw new UnsupportedOperationException(
"Unable to determine if element is selected. Tag name is: " + element.getTagName());
}
@Override
public boolean isEnabled() {
assertElementNotStale();
return !element.hasAttribute("disabled");
}
@Override
public boolean isDisplayed() {
assertElementNotStale();
return element.isDisplayed();
}
@Override
public Point getLocation() {
assertElementNotStale();
try {
return new Point(readAndRound("left"), readAndRound("top"));
} catch (Exception e) {
throw new WebDriverException("Cannot determine size of element", e);
}
}
@Override
public Dimension getSize() {
assertElementNotStale();
try {
final int width = readAndRound("width");
final int height = readAndRound("height");
return new Dimension(width, height);
} catch (Exception e) {
throw new WebDriverException("Cannot determine size of element", e);
}
}
@Override
public Rectangle getRect() {
return new Rectangle(getLocation(), getSize());
}
private int readAndRound(final String property) {
final String cssValue = getCssValue(property).replaceAll("[^0-9\\.]", "");
if (cssValue.isEmpty()) {
return 5; // wrong... but better than nothing
}
return Math.round(Float.parseFloat(cssValue));
}
@Override
public String getText() {
assertElementNotStale();
return HtmlSerializer.getText(element);
}
protected HtmlUnitDriver getParent() {
return parent;
}
protected DomElement getElement() {
return element;
}
@Deprecated // It's not a part of WebDriver API
public List getElementsByTagName(String tagName) {
assertElementNotStale();
List allChildren = element.getByXPath(".//" + tagName);
List elements = new ArrayList<>();
for (Object o : allChildren) {
if (!(o instanceof HtmlElement)) {
continue;
}
HtmlElement child = (HtmlElement) o;
elements.add(getParent().toWebElement(child));
}
return elements;
}
@Override
public WebElement findElement(By by) {
assertElementNotStale();
return parent.findElement(by, this);
}
@Override
public List findElements(By by) {
assertElementNotStale();
return parent.findElements(by, this);
}
@Override
public WebElement findElementById(String id) {
assertElementNotStale();
return findElementByXPath(".//*[@id = '" + id + "']");
}
@Override
public List findElementsById(String id) {
assertElementNotStale();
return findElementsByXPath(".//*[@id = '" + id + "']");
}
@Override
public List findElementsByCssSelector(String using) {
List allElements = parent.findElementsByCssSelector(using);
return findChildNodes(allElements);
}
@Override
public WebElement findElementByCssSelector(String using) {
List allElements = parent.findElementsByCssSelector(using);
allElements = findChildNodes(allElements);
if (allElements.isEmpty()) {
throw new NoSuchElementException("Cannot find child element using css: " + using);
}
return allElements.get(0);
}
private List findChildNodes(List allElements) {
List toReturn = new LinkedList<>();
for (WebElement current : allElements) {
DomElement candidate = ((HtmlUnitWebElement) current).element;
if (element.isAncestorOf(candidate) && element != candidate) {
toReturn.add(current);
}
}
return toReturn;
}
@Override
public WebElement findElementByXPath(String xpathExpr) {
assertElementNotStale();
Object node;
try {
node = element.getFirstByXPath(xpathExpr);
} catch (Exception ex) {
// The xpath expression cannot be evaluated, so the expression is invalid
throw new InvalidSelectorException(
String.format(HtmlUnitDriver.INVALIDXPATHERROR, xpathExpr), ex);
}
if (node == null) {
throw new NoSuchElementException("Unable to find an element with xpath " + xpathExpr);
}
if (node instanceof HtmlElement) {
return getParent().toWebElement((HtmlElement) node);
}
// The xpath selector selected something different than a WebElement. The selector is therefore
// invalid
throw new InvalidSelectorException(
String.format(HtmlUnitDriver.INVALIDSELECTIONERROR, xpathExpr, node.getClass().toString()));
}
@Override
public List findElementsByXPath(String xpathExpr) {
assertElementNotStale();
List webElements = new ArrayList<>();
List domElements;
try {
domElements = element.getByXPath(xpathExpr);
} catch (Exception ex) {
// The xpath expression cannot be evaluated, so the expression is invalid
throw new InvalidSelectorException(
String.format(HtmlUnitDriver.INVALIDXPATHERROR, xpathExpr), ex);
}
for (Object e : domElements) {
if (e instanceof DomElement) {
webElements.add(getParent().toWebElement((DomElement) e));
}
else {
// The xpath selector selected something different than a WebElement. The selector is
// therefore invalid
throw new InvalidSelectorException(
String.format(HtmlUnitDriver.INVALIDSELECTIONERROR,
xpathExpr, e.getClass().toString()));
}
}
return webElements;
}
@Override
public WebElement findElementByLinkText(String linkText) {
assertElementNotStale();
List elements = findElementsByLinkText(linkText);
if (elements.isEmpty()) {
throw new NoSuchElementException("Unable to find element with linkText " + linkText);
}
return elements.get(0);
}
@Override
public List findElementsByLinkText(String linkText) {
assertElementNotStale();
String expectedText = linkText.trim();
List htmlElements = ((HtmlElement) element).getElementsByTagName("a");
List webElements = new ArrayList<>();
for (DomElement e : htmlElements) {
if (expectedText.equals(e.getTextContent().trim()) && e.getAttribute("href") != null) {
webElements.add(getParent().toWebElement(e));
}
}
return webElements;
}
@Override
public WebElement findElementByPartialLinkText(String linkText) {
assertElementNotStale();
List elements = findElementsByPartialLinkText(linkText);
if (elements.isEmpty()) {
throw new NoSuchElementException(
"Unable to find element with linkText " + linkText);
}
return elements.size() > 0 ? elements.get(0) : null;
}
@Override
public List findElementsByPartialLinkText(String linkText) {
assertElementNotStale();
List htmlElements = ((HtmlElement) element).getElementsByTagName("a");
List webElements = new ArrayList<>();
for (HtmlElement e : htmlElements) {
if (e.getTextContent().contains(linkText)
&& e.getAttribute("href") != null) {
webElements.add(getParent().toWebElement(e));
}
}
return webElements;
}
@Override
public WebElement findElementByTagName(String name) {
assertElementNotStale();
List elements = findElementsByTagName(name);
if (elements.isEmpty()) {
throw new NoSuchElementException("Cannot find element with tag name: " + name);
}
return elements.get(0);
}
@Override
public List findElementsByTagName(String name) {
assertElementNotStale();
List elements = ((HtmlElement) element).getElementsByTagName(name);
List toReturn = new ArrayList<>(elements.size());
for (HtmlElement e : elements) {
toReturn.add(parent.toWebElement(e));
}
return toReturn;
}
private HtmlUnitWebElement findParentForm() {
DomNode current = element;
while (!(current == null || current instanceof HtmlForm)) {
current = current.getParentNode();
}
return getParent().toWebElement((HtmlForm) current);
}
@Override
public String toString() {
if (toString == null) {
StringBuilder sb = new StringBuilder();
sb.append('<').append(element.getTagName());
NamedNodeMap attributes = element.getAttributes();
int n = attributes.getLength();
for (int i = 0; i < n; ++i) {
Attr a = (Attr) attributes.item(i);
sb.append(' ').append(a.getName()).append("=\"")
.append(a.getValue().replace("\"", """)).append("\"");
}
if (element.hasChildNodes()) {
sb.append('>');
} else {
sb.append(" />");
}
toString = sb.toString();
}
return toString;
}
protected void assertElementNotStale() {
parent.assertElementNotStale(element);
}
@Override
public String getCssValue(String propertyName) {
assertElementNotStale();
final HTMLElement elem = (HTMLElement) ((HtmlElement) element).getScriptableObject();
String style = elem.getWindow().getComputedStyle(elem, null).getPropertyValue(propertyName);
return getColor(style);
}
private static String getColor(String name) {
if ("null".equals(name)) {
return "transparent";
}
if (name.startsWith("rgb(")) {
return Color.fromString(name).asRgba();
}
Colors colors = getColorsOf(name);
if (colors != null) {
return colors.getColorValue().asRgba();
}
return name;
}
private static Colors getColorsOf(String name) {
name = name.toUpperCase();
for (Colors colors : Colors.values()) {
if (colors.name().equals(name)) {
return colors;
}
}
return null;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof WebElement)) {
return false;
}
WebElement other = (WebElement) obj;
if (other instanceof WrapsElement) {
other = ((WrapsElement) obj).getWrappedElement();
}
return other instanceof HtmlUnitWebElement &&
element.equals(((HtmlUnitWebElement) other).element);
}
@Override
public int hashCode() {
return element.hashCode();
}
/*
* (non-Javadoc)
*
* @see org.openqa.selenium.WrapsDriver#getContainingDriver()
*/
@Override
public WebDriver getWrappedDriver() {
return parent;
}
@Override
public Coordinates getCoordinates() {
return this;
}
@Override
public Point onScreen() {
throw new UnsupportedOperationException("Not displayed, no screen location.");
}
@Override
public Point inViewPort() {
return getLocation();
}
@Override
public Point onPage() {
return getLocation();
}
@Override
public Object getAuxiliary() {
return element;
}
@Override
public X getScreenshotAs(OutputType outputType) throws WebDriverException {
throw new UnsupportedOperationException(
"Screenshots are not enabled for HtmlUnitDriver");
}
public int getId() {
return id;
}
}