com.vaadin.testbench.TestBenchElement Maven / Gradle / Ivy
/**
* Copyright (C) 2000-2022 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.testbench;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import com.vaadin.testbench.commands.CanCompareScreenshots;
import com.vaadin.testbench.commands.ScreenshotComparator;
import com.vaadin.testbench.commands.TestBenchCommandExecutor;
import com.vaadin.testbench.commands.TestBenchCommands;
import com.vaadin.testbench.elementsbase.Element;
import com.vaadin.testbench.parallel.BrowserUtil;
import elemental.json.Json;
import elemental.json.JsonValue;
/**
* TestBenchElement is a WebElement wrapper. It provides Vaadin specific helper
* functionality. TestBenchElements are created when you search for elements
* from TestBenchTestCase or a context relative search from TestBenchElement.
*/
@Element("*")
public class TestBenchElement implements WrapsElement, WebElement, HasDriver,
CanCompareScreenshots, HasTestBenchCommandExecutor, HasElementQuery,
HasPropertySettersGetters, HasCallFunction {
private WebElement wrappedElement = null;
private TestBenchCommandExecutor commandExecutor = null;
protected TestBenchElement() {
}
protected TestBenchElement(WebElement webElement,
TestBenchCommandExecutor commandExecutor) {
init(webElement, commandExecutor);
}
/**
* TestBenchElement initialization function. If a subclass of
* TestBenchElement needs to run some initialization code, it should
* override {@link #init()}, not this function.
*
* @param element
* WebElement to wrap
* @param commandExecutor
* TestBenchCommandExecutor instance
*/
protected void init(WebElement element,
TestBenchCommandExecutor commandExecutor) {
if (this.commandExecutor == null) {
this.commandExecutor = commandExecutor;
wrappedElement = element;
init();
}
}
/**
* Checks if the current test is running on Chrome.
*
* @return true
if the test is running on Chrome,
* false
otherwise
*/
protected boolean isChrome() {
return BrowserUtil.isChrome(getCapabilities());
}
/**
* Checks if the current test is running on Firefox.
*
* @return true
if the test is running on Firefox,
* false
otherwise
*/
protected boolean isFirefox() {
return BrowserUtil.isFirefox(getCapabilities());
}
/**
* Returns information about current browser used
*
* @see Capabilities
* @return information about current browser used
*/
protected Capabilities getCapabilities() {
WebDriver driver = getDriver();
if (driver instanceof HasCapabilities) {
return ((HasCapabilities) driver).getCapabilities();
} else {
return null;
}
}
/**
* This is run after initializing a TestBenchElement. This can be overridden
* in subclasses of TestBenchElement to run some initialization code.
*/
protected void init() {
}
@Override
public WebElement getWrappedElement() {
return wrappedElement;
}
protected void waitForVaadin() {
if (getCommandExecutor() != null) {
getCommandExecutor().waitForVaadin();
}
}
/**
* Sets the number of pixels that an element's content is scrolled from the
* top.
*
* @param scrollTop
* value set to Element.scroll property
*/
public void scroll(int scrollTop) {
setProperty("scrollTop", scrollTop);
}
/**
* Sets the number of pixels that an element's content is scrolled to the
* left.
*
* @param scrollLeft
* value set to Element.scrollLeft property
*/
public void scrollLeft(int scrollLeft) {
setProperty("scrollLeft", scrollLeft);
}
@Override
public void click() {
try {
// Avoid strange "element not clickable at point" problems
callFunction("click");
} catch (Exception e) {
if (e.getMessage()
.contains("Inspected target navigated or closed")) {
// This happens with chromedriver although e.g. navigation
// succeeds
return;
}
// SVG elements and maybe others do not have a 'click' method
autoScrollIntoView();
waitForVaadin();
wrappedElement.click();
}
}
@Override
public void submit() {
click();
}
@Override
public void sendKeys(CharSequence... keysToSend) {
autoScrollIntoView();
waitForVaadin();
wrappedElement.sendKeys(keysToSend);
}
@Override
public void clear() {
autoScrollIntoView();
waitForVaadin();
wrappedElement.clear();
}
@Override
public String getTagName() {
waitForVaadin();
return wrappedElement.getTagName();
}
@Override
public String getAttribute(String name) {
waitForVaadin();
return wrappedElement.getAttribute(name);
}
/**
* Checks if the given attribute is present on the element.
*
* @param attribute
* the name of the attribute
* @return true
if the attribute is present, false
* otherwise
*/
public boolean hasAttribute(String attribute) {
return (boolean) callFunction("hasAttribute", attribute);
}
@Override
public boolean isSelected() {
autoScrollIntoView();
waitForVaadin();
return wrappedElement.isSelected();
}
/**
* Returns whether the Vaadin component, that this element represents, is
* enabled or not.
*
* @return true if the component is enabled.
*/
@Override
public boolean isEnabled() {
waitForVaadin();
return !hasClassName("v-disabled") && !hasAttribute("disabled")
&& wrappedElement.isEnabled();
}
@Override
public String getText() {
autoScrollIntoView();
waitForVaadin();
return wrappedElement.getText();
}
@Override
public List findElements(By by) {
return (List) TestBenchElement.wrapElements(
wrappedElement.findElements(by), getCommandExecutor());
}
@Override
public TestBenchElement findElement(By by) {
waitForVaadin();
return wrapElement(wrappedElement.findElement(by),
getCommandExecutor());
}
@Override
public boolean isDisplayed() {
waitForVaadin();
return wrappedElement.isDisplayed();
}
@Override
public Point getLocation() {
waitForVaadin();
return wrappedElement.getLocation();
}
@Override
public Dimension getSize() {
waitForVaadin();
return wrappedElement.getSize();
}
@Override
public String getCssValue(String propertyName) {
waitForVaadin();
return wrappedElement.getCssValue(propertyName);
}
public void click(int x, int y, Keys... modifiers) {
autoScrollIntoView();
waitForVaadin();
Actions actions = new Actions(getDriver());
actions.moveToElement(wrappedElement, x, y);
// Press any modifier keys
for (Keys modifier : modifiers) {
actions.keyDown(modifier);
}
actions.click();
// Release any modifier keys
for (Keys modifier : modifiers) {
actions.keyUp(modifier);
}
actions.build().perform();
}
public void doubleClick() {
autoScrollIntoView();
waitForVaadin();
new Actions(getDriver()).doubleClick(wrappedElement).build().perform();
}
public void contextClick() {
autoScrollIntoView();
waitForVaadin();
new Actions(getDriver()).contextClick(wrappedElement).build().perform();
}
public T wrap(Class elementType) {
return TestBench.wrap(this, elementType);
}
@Override
public TestBenchCommandExecutor getCommandExecutor() {
return commandExecutor;
}
@Override
public WebDriver getDriver() {
return getCommandExecutor().getDriver();
}
/**
* Returns this TestBenchElement cast to a SearchContext. Method provided
* for compatibility and consistency.
*/
@Override
public SearchContext getContext() {
return this;
}
/**
* Move browser focus to this Element
*/
public void focus() {
waitForVaadin();
getCommandExecutor().focusElement(this);
}
protected static List wrapElements(
List elements,
TestBenchCommandExecutor commandExecutor) {
List wrappedList = new ArrayList<>();
for (WebElement e : elements) {
wrappedList.add(wrapElement(e, commandExecutor));
}
return wrappedList;
}
protected static TestBenchElement wrapElement(WebElement element,
TestBenchCommandExecutor commandExecutor) {
if (element instanceof TestBenchElement) {
return (TestBenchElement) element;
} else {
return TestBench.createElement(element, commandExecutor);
}
}
@Override
public X getScreenshotAs(OutputType target)
throws WebDriverException {
waitForVaadin();
return wrappedElement.getScreenshotAs(target);
}
@Override
public Rectangle getRect() {
waitForVaadin();
return wrappedElement.getRect();
}
/**
* Gets all the class names set for this element.
*
* @return a set of class names
*/
public Set getClassNames() {
String classAttribute = getAttribute("class");
Set classes = new HashSet<>();
if (classAttribute == null) {
return classes;
}
classAttribute = classAttribute.trim();
if (classAttribute.isEmpty()) {
return classes;
}
Collections.addAll(classes, classAttribute.split("[ ]+"));
return classes;
}
/**
* Checks if this element has the given class name.
*
* Matches only full class names, i.e. has ("foo") does not match
* class="foobar bafoo"
*
* @param className
* the class name to check for
* @return true
if the element has the given class name,
* false
otherwise
*/
public boolean hasClassName(String className) {
if (className == null || className.isEmpty()) {
return false;
}
return getClassNames().contains(className);
}
@Override
public boolean equals(Object obj) {
if (wrappedElement == null) {
return false;
}
return wrappedElement.equals(obj);
}
@Override
public int hashCode() {
if (wrappedElement == null) {
return 32;
}
return wrappedElement.hashCode();
}
@Override
public boolean compareScreen(String referenceId) throws IOException {
return ScreenshotComparator.compareScreen(referenceId,
getCommandExecutor().getReferenceNameGenerator(),
getCommandExecutor().getImageComparison(), this,
(HasCapabilities) getDriver());
}
@Override
public boolean compareScreen(File reference) throws IOException {
return ScreenshotComparator.compareScreen(reference,
getCommandExecutor().getImageComparison(),
(TakesScreenshot) this, (HasCapabilities) getDriver());
}
@Override
public boolean compareScreen(BufferedImage reference, String referenceName)
throws IOException {
return ScreenshotComparator.compareScreen(reference, referenceName,
getCommandExecutor().getImageComparison(),
(TakesScreenshot) this, (HasCapabilities) getDriver());
}
/***
* Scrolls the element into the visible area of the browser window
*/
public void scrollIntoView() {
callFunction("scrollIntoView");
}
/**
* Scrolls the element into the visible area of the browser window if
* {@link TestBenchCommands#isAutoScrollIntoView()} is enabled and the
* element is not displayed
*/
private void autoScrollIntoView() {
try {
if (getCommandExecutor().isAutoScrollIntoView()) {
if (!wrappedElement.isDisplayed()) {
scrollIntoView();
}
}
} catch (Exception e) {
}
}
/**
* Waits the given number of seconds for the given condition to become
* neither null nor false. {@link NotFoundException}s are ignored by
* default.
*
* Use e.g. as
* waitUntil(ExpectedConditions.presenceOfElementLocated(by), 10);
*
* @param
* The return type of the {@link ExpectedCondition} and this
* method
* @param condition
* Models a condition that might reasonably be expected to
* eventually evaluate to something that is neither null nor
* false.
* @param timeoutInSeconds
* The timeout in seconds for the wait.
* @return The condition's return value if it returned something different
* from null or false before the timeout expired.
*
* @throws TimeoutException
* If the timeout expires.
*
* @see FluentWait#until
* @see ExpectedCondition
*/
protected T waitUntil(ExpectedCondition condition,
long timeoutInSeconds) {
return new WebDriverWait(getDriver(),
Duration.ofSeconds(timeoutInSeconds)).until(condition);
}
/**
* Waits up to 10 seconds for the given condition to become neither null nor
* false. {@link NotFoundException}s are ignored by default.
*
* Use e.g. as
* waitUntil(ExpectedConditions.presenceOfElementLocated(by));
*
* @param
* The return type of the {@link ExpectedCondition} and this
* method
* @param condition
* Models a condition that might reasonably be expected to
* eventually evaluate to something that is neither null nor
* false.
* @return The condition's return value if it returned something different
* from null or false before the timeout expired.
*
* @throws TimeoutException
* If 10 seconds passed.
*
* @see FluentWait#until
* @see ExpectedCondition
*/
protected T waitUntil(ExpectedCondition condition) {
return waitUntil(condition, 10);
}
@Override
public void setProperty(String name, String value) {
internalSetProperty(name, value);
}
@Override
public void setProperty(String name, Boolean value) {
internalSetProperty(name, value);
}
@Override
public void setProperty(String name, Double value) {
internalSetProperty(name, value);
}
@Override
public void setProperty(String name, Integer value) {
internalSetProperty(name, value);
}
@Override
public String getPropertyString(String... propertyNames) {
Object value = getProperty(propertyNames);
if (value == null) {
return null;
}
return createJsonValue(value).asString();
}
@Override
public Boolean getPropertyBoolean(String... propertyNames) {
Object value = getProperty(propertyNames);
if (value == null) {
return null;
}
return createJsonValue(value).asBoolean();
}
@Override
public TestBenchElement getPropertyElement(String... propertyNames) {
return (TestBenchElement) getProperty(propertyNames);
}
@Override
public List getPropertyElements(String... propertyNames) {
return (List) getProperty(propertyNames);
}
@Override
public Double getPropertyDouble(String... propertyNames) {
Object value = getProperty(propertyNames);
if (value == null) {
return null;
}
return createJsonValue(value).asNumber();
}
@Override
public Integer getPropertyInteger(String... propertyNames) {
Double number = getPropertyDouble(propertyNames);
return (number == null) ? null : number.intValue();
}
private void internalSetProperty(String name, Object value) {
if (isFirefox() && value instanceof Double) {
executeScript("arguments[0][arguments[1]]=Number(arguments[2])",
this, name, String.valueOf((value)));
} else {
executeScript("arguments[0][arguments[1]]=arguments[2]", this, name,
value);
}
}
@Override
public Object getProperty(String... propertyNames) {
String script = "var value = arguments[0];"
+ createPropertyChain(propertyNames) + ";";
Object[] jsParameters = Stream
.concat(Stream.of(this), Stream.of(propertyNames)).toArray();
if (isFirefox()) {
String isNumberScript = script + "return typeof value == 'number';";
boolean number = (boolean) executeScript(isNumberScript,
jsParameters);
if (number) {
String str = (String) executeScript(
script + "return value.toString();", jsParameters);
return Double.parseDouble(str);
}
}
return executeScript(
script + "return value;",
jsParameters);
}
private static String createPropertyChain(String[] propertyNames) {
String result = "";
for (int i = 0; i < propertyNames.length; i++) {
result += "if (typeof value != 'undefined') value = value[arguments["
+ (i + 1) + "]];";
}
return result;
}
private JsonValue createJsonValue(Object value) {
if (value == null) {
return Json.createNull();
} else if (value instanceof String) {
return Json.create((String) value);
} else if (value instanceof Number) {
return Json.create(((Number) value).doubleValue());
} else if (value instanceof Boolean) {
return Json.create((Boolean) value);
} else {
throw new IllegalArgumentException(
"Type of property is unsupported: "
+ value.getClass().getName());
}
}
/**
* Executes the given JavaScript in the context of the currently selected
* frame or window. The script fragment provided will be executed as the
* body of an anonymous function.
*
* This method wraps any returned {@link WebElement} as
* {@link TestBenchElement}.
*
* @param script
* the script to execute
* @param args
* the arguments, available in the script as
* {@code arguments[0]...arguments[N]}
* @return whatever
* {@link JavascriptExecutor#executeScript(String, Object...)}
* returns
* @throws UnsupportedOperationException
* if the underlying driver does not support JavaScript
* execution
* @see JavascriptExecutor#executeScript(String, Object...)
*/
protected Object executeScript(String script, Object... args) {
return getCommandExecutor().executeScript(script, args);
}
@Override
public Object callFunction(String methodName, Object... args) {
// arguments[0].method(arguments[1],arguments[2],arguments[3])
String paramPlaceholderString = IntStream.range(1, args.length + 1)
.mapToObj(i -> "arguments[" + i + "]")
.collect(Collectors.joining(","));
Object[] jsParameters = Stream.concat(Stream.of(this), Stream.of(args))
.toArray(size -> new Object[size]);
return executeScript("return arguments[0]." + methodName + "("
+ paramPlaceholderString + ")", jsParameters);
}
/**
* Dispatches (fires) a custom event of the given type on the element.
*
* The event is created without any parameters.
*
* @param eventType
* the type of custom event to dispatch
*/
public void dispatchEvent(String eventType) {
executeScript(
"arguments[0].dispatchEvent(new CustomEvent(arguments[1]));",
this, eventType);
}
/**
* Dispatches (fires) a custom event of the given type on the element with
* the given properties
*
* @param eventType
* the type of custom event to dispatch
* @param customEventInit
* map with properties and values that will be used to initialize
* the event
*/
public void dispatchEvent(String eventType,
Map customEventInit) {
executeScript(
"arguments[0].dispatchEvent(new CustomEvent(arguments[1], arguments[2]));",
this, eventType, customEventInit);
}
}