fluentlenium-core/src/main/java/io/fluentlenium/core/domain/FluentWebElement.java
package io.fluentlenium.core.domain;
import io.appium.java_client.AppiumDriver;
import io.fluentlenium.core.FluentControl;
import io.fluentlenium.core.FluentPage;
import io.fluentlenium.core.SeleniumDriverControl;
import io.fluentlenium.core.action.Fill;
import io.fluentlenium.core.action.FillSelect;
import io.fluentlenium.core.action.FluentActions;
import io.fluentlenium.core.action.FluentJavascriptActionsImpl;
import io.fluentlenium.core.action.KeyboardElementActions;
import io.fluentlenium.core.action.MouseElementActions;
import io.fluentlenium.core.action.WindowAction;
import io.fluentlenium.core.alert.Alert;
import io.fluentlenium.core.alert.AlertControl;
import io.fluentlenium.core.capabilities.CapabilitiesControl;
import io.fluentlenium.core.components.ComponentInstantiator;
import io.fluentlenium.core.conditions.FluentConditions;
import io.fluentlenium.core.conditions.WebElementConditions;
import io.fluentlenium.core.css.CssControl;
import io.fluentlenium.core.css.CssSupport;
import io.fluentlenium.core.dom.Dom;
import io.fluentlenium.core.events.EventsControl;
import io.fluentlenium.core.events.EventsRegistry;
import io.fluentlenium.core.hook.FluentHook;
import io.fluentlenium.core.hook.HookControl;
import io.fluentlenium.core.hook.HookControlImpl;
import io.fluentlenium.core.hook.HookDefinition;
import io.fluentlenium.core.inject.ContainerContext;
import io.fluentlenium.core.inject.FluentInjectControl;
import io.fluentlenium.core.inject.Unshadower;
import io.fluentlenium.core.label.FluentLabel;
import io.fluentlenium.core.label.FluentLabelImpl;
import io.fluentlenium.core.navigation.NavigationControl;
import io.fluentlenium.core.proxy.FluentProxyState;
import io.fluentlenium.core.proxy.LocatorHandler;
import io.fluentlenium.core.proxy.LocatorProxies;
import io.fluentlenium.core.script.FluentJavascript;
import io.fluentlenium.core.script.JavascriptControl;
import io.fluentlenium.core.search.Search;
import io.fluentlenium.core.search.SearchControl;
import io.fluentlenium.core.search.SearchFilter;
import io.fluentlenium.core.snapshot.SnapshotControl;
import io.fluentlenium.core.wait.FluentWaitElement;
import io.fluentlenium.utils.SupplierOfInstance;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.ElementNotInteractableException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.ui.ExpectedConditions;
import javax.annotation.Nullable;
import java.io.File;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.function.Function;
/**
* Wraps a Selenium {@link WebElement}. It provides an enhanced API to control selenium element.
*/
@SuppressWarnings({"PMD.GodClass", "PMD.ExcessivePublicCount"})
public class FluentWebElement extends Component
implements WrapsElement, FluentActions<FluentWebElement, FluentWebElement>, FluentProxyState<FluentWebElement>,
SearchControl<FluentWebElement>, HookControl<FluentWebElement>, FluentLabel<FluentWebElement>,
NavigationControl, JavascriptControl, AlertControl, SnapshotControl, EventsControl, SeleniumDriverControl,
CssControl, FluentInjectControl, CapabilitiesControl, ComponentInstantiator {
private final Search search;
private final Dom dom;
private final MouseElementActions mouseActions;
private final KeyboardElementActions keyboardActions;
private final WebElementConditions conditions;
private final HookControlImpl<FluentWebElement> hookControl;
private final FluentLabel<FluentWebElement> label;
private final FluentJavascriptActionsImpl<FluentWebElement> javascriptActions;
/**
* Creates a new fluent web element.
*
* @param element underlying element
* @param control control interface
* @param instantiator component instantiator
*/
public FluentWebElement(WebElement element, FluentControl control, ComponentInstantiator instantiator) {
super(element, control, instantiator);
hookControl = new HookControlImpl<>(this, webElement, this.control, this.instantiator,
/*do not change it to lambda - change will affect w/ PMD warning
Overridable method 'getElement' called during object construction*/
() -> {
LocatorHandler locatorHandler = LocatorProxies.getLocatorHandler(getElement());
ElementLocator locator = locatorHandler.getLocator();
WebElement noHookElement = LocatorProxies.createWebElement(locator);
return control.newComponent(this.getClass(), noHookElement);
});
search = new Search(element, this, this.instantiator, this.control);
dom = new Dom(element, this.instantiator);
mouseActions = new MouseElementActions(this.control.getDriver(), element);
keyboardActions = new KeyboardElementActions(this.control.getDriver(), element);
conditions = new WebElementConditions(this);
label = new FluentLabelImpl<>(this, () -> getElement().toString());
javascriptActions = new FluentJavascriptActionsImpl<>(this, this.control, new SupplierOfInstance<>(this));
unshadowAllFields();
}
/**
* Creates a new fluent web element.
*
* @param element underlying element
* @param control control interface
* @param instantiator component instantiator
*/
public FluentWebElement(WebElement element, FluentControl control, ComponentInstantiator instantiator, Actions actions) {
super(element, control, instantiator);
hookControl = new HookControlImpl<>(this, webElement, this.control, this.instantiator,
/*do not change it to lambda - change will affect w/ PMD warning
Overridable method 'getElement' called during object construction*/
() -> {
LocatorHandler locatorHandler = LocatorProxies.getLocatorHandler(getElement());
ElementLocator locator = locatorHandler.getLocator();
WebElement noHookElement = LocatorProxies.createWebElement(locator);
return control.newComponent(this.getClass(), noHookElement);
});
search = new Search(element, this, this.instantiator, this.control);
dom = new Dom(element, this.instantiator);
mouseActions = new MouseElementActions(this.control.getDriver(), element, actions);
keyboardActions = new KeyboardElementActions(this.control.getDriver(), element, actions);
conditions = new WebElementConditions(this);
label = new FluentLabelImpl<>(this, () -> getElement().toString());
javascriptActions = new FluentJavascriptActionsImpl<>(this, this.control, new SupplierOfInstance<>(this));
unshadowAllFields();
}
public FluentControl getFluentControl() {
return control;
}
public void unshadowAllFields() {
if (getDriver() != null) {
new Unshadower(getDriver(), this).unshadowAllAnnotatedFields();
}
}
private HookControl<FluentWebElement> getHookControl() { // NOPMD UnusedPrivateMethod
return hookControl;
}
private FluentJavascriptActionsImpl<FluentWebElement> getJavascriptActions() { //NOPMD UnusedPrivateMethod
return javascriptActions;
}
public FluentLabel<FluentWebElement> getLabel() {
return label;
}
@Override
public FluentJavascript executeScript(String script, Object... args) {
return control.executeScript(script, args);
}
@Override
public FluentJavascript executeAsyncScript(String script, Object... args) {
return control.executeAsyncScript(script, args);
}
@Override
public Alert alert() {
return control.alert();
}
@Override
public void takeHtmlDump() {
control.takeHtmlDump();
}
@Override
public void takeHtmlDump(String fileName) {
control.takeHtmlDump(fileName);
}
@Override
public boolean canTakeScreenShot() {
return control.canTakeScreenShot();
}
@Override
public File takeScreenshot() {
return control.takeScreenshot();
}
@Override
public File takeScreenshot(String fileName) {
return control.takeScreenshot(fileName);
}
@Override
public EventsRegistry events() {
return control.events();
}
@Override
public <P extends FluentPage> P goTo(P page) {
return control.goTo(page);
}
@Override
public void goTo(String url) {
control.goTo(url);
}
@Override
public void goToInNewTab(String url) {
control.goToInNewTab(url);
}
@Override
public void switchTo(FluentList<? extends FluentWebElement> elements) {
control.switchTo(elements);
}
@Override
public void switchTo(FluentWebElement element) {
control.switchTo(element);
}
@Override
public void switchTo() {
control.switchTo();
}
@Override
public void switchToDefault() {
control.switchToDefault();
}
@Override
public String pageSource() {
return control.pageSource();
}
@Override
public WindowAction window() {
return control.window();
}
@Override
public Set<Cookie> getCookies() {
return control.getCookies();
}
@Override
public Cookie getCookie(String name) {
return control.getCookie(name);
}
@Override
public String url() {
return control.url();
}
@Override
public WebDriver getDriver() {
return control.getDriver();
}
@Override
public AppiumDriver getAppiumDriver() {
return control.getAppiumDriver();
}
@Override
public CssSupport css() {
return control.css();
}
@Override
public ContainerContext inject(Object container) {
return control.inject(container);
}
@Override
public ContainerContext injectComponent(Object componentContainer, Object parentContainer, SearchContext context) {
return control.injectComponent(componentContainer, parentContainer, context);
}
@Override
public <T> T newInstance(Class<T> cls) {
return control.newInstance(cls);
}
@Override
public FluentWebElement newFluent(WebElement element) {
return control.newFluent(element);
}
@Override
public <T> T newComponent(Class<T> componentClass, WebElement element) {
return control.newComponent(componentClass, element);
}
@Override
public FluentList<FluentWebElement> newFluentList() {
return control.newFluentList();
}
@Override
public FluentList<FluentWebElement> newFluentList(FluentWebElement... elements) {
return control.newFluentList(elements);
}
@Override
public FluentList<FluentWebElement> newFluentList(List<FluentWebElement> elements) {
return control.newFluentList(elements);
}
@Override
public FluentList<FluentWebElement> asFluentList(WebElement... elements) {
return control.asFluentList(elements);
}
@Override
public FluentList<FluentWebElement> asFluentList(Iterable<WebElement> elements) {
return control.asFluentList(elements);
}
@Override
public FluentList<FluentWebElement> asFluentList(List<WebElement> elements) {
return control.asFluentList(elements);
}
@Override
public <T extends FluentWebElement> FluentList<T> newFluentList(Class<T> componentClass) {
return control.newFluentList(componentClass);
}
@Override
public <T extends FluentWebElement> FluentList<T> newFluentList(Class<T> componentClass, T... elements) {
return control.newFluentList(componentClass, elements);
}
@Override
public <T extends FluentWebElement> FluentList<T> newFluentList(Class<T> componentClass, List<T> elements) {
return control.newFluentList(componentClass, elements);
}
@Override
public <T extends FluentWebElement> FluentList<T> asFluentList(Class<T> componentClass, WebElement... elements) {
return control.asFluentList(componentClass, elements);
}
@Override
public <T extends FluentWebElement> FluentList<T> asFluentList(Class<T> componentClass, Iterable<WebElement> elements) {
return control.asFluentList(componentClass, elements);
}
@Override
public <T extends FluentWebElement> FluentList<T> asFluentList(Class<T> componentClass, List<WebElement> elements) {
return control.asFluentList(componentClass, elements);
}
@Override
public <T> ComponentList<T> newComponentList(Class<T> componentClass) {
return control.newComponentList(componentClass);
}
@Override
public <T> ComponentList<T> asComponentList(Class<T> componentClass, WebElement... elements) {
return control.asComponentList(componentClass, elements);
}
@Override
public <T> ComponentList asComponentList(Class<T> componentClass, Iterable<WebElement> elements) {
return control.asComponentList(componentClass, elements);
}
@Override
public <T> ComponentList<T> asComponentList(Class<T> componentClass, List<WebElement> elements) {
return control.asComponentList(componentClass, elements);
}
@Override
public <T> ComponentList<T> newComponentList(Class<T> componentClass, T... componentsList) {
return control.newComponentList(componentClass, componentsList);
}
@Override
public <T> ComponentList<T> newComponentList(Class<T> componentClass, List<T> componentsList) {
return control.newComponentList(componentClass, componentsList);
}
@Override
public <L extends List<T>, T> L newComponentList(Class<L> listClass, Class<T> componentClass) {
return control.newComponentList(listClass, componentClass);
}
@Override
public <L extends List<T>, T> L asComponentList(Class<L> listClass, Class<T> componentClass, WebElement... elements) {
return control.asComponentList(listClass, componentClass, elements);
}
@Override
public <L extends List<T>, T> L asComponentList(Class<L> listClass, Class<T> componentClass, Iterable<WebElement> elements) {
return control.asComponentList(listClass, componentClass, elements);
}
@Override
public <L extends List<T>, T> L asComponentList(Class<L> listClass, Class<T> componentClass, List<WebElement> elements) {
return control.asComponentList(listClass, componentClass, elements);
}
@Override
public <L extends List<T>, T> L newComponentList(Class<L> listClass, Class<T> componentClass, T... componentsList) {
return control.newComponentList(listClass, componentClass, componentsList);
}
@Override
public <L extends List<T>, T> L newComponentList(Class<L> listClass, Class<T> componentClass, List<T> componentsList) {
return control.newComponentList(listClass, componentClass, componentsList);
}
@Override
public boolean isComponentClass(Class<?> componentClass) {
return control.isComponentClass(componentClass);
}
@Override
public boolean isComponentListClass(Class<? extends List<?>> componentListClass) {
return false;
}
@Override
public Capabilities capabilities() {
return control.capabilities();
}
@Override
public FluentWebElement click() {
webElement.click();
return this;
}
@Override
public FluentWebElement doubleClick() {
mouse().doubleClick();
return this;
}
@Override
public FluentWebElement contextClick() {
mouse().contextClick();
return this;
}
@Override
public FluentWebElement waitAndClick() {
return waitAndClick(Duration.ofSeconds(5));
}
@Override
public FluentWebElement waitAndClick(Duration duration) {
await().atMost(duration).until(this).clickable();
this.scrollToCenter();
this.click();
return this;
}
@Override
public FluentWebElement hoverOver() {
mouse().moveToElement();
return this;
}
@Override
public boolean present() {
return LocatorProxies.present(webElement);
}
@Override
public FluentWebElement now() {
LocatorProxies.now(webElement);
return this;
}
@Override
public FluentWebElement now(boolean force) {
if (force) {
reset();
}
return now();
}
@Override
public FluentWebElement reset() {
LocatorProxies.reset(webElement);
return this;
}
@Override
public boolean loaded() {
return LocatorProxies.loaded(webElement);
}
/**
* XPath Axes accessor (parent, ancestors, preceding, following, ...).
*
* @return object to perform XPath Axes transformations.
* @deprecated Use {@link #dom()} instead.
*/
@Deprecated
public Dom axes() {
return dom;
}
/**
* XPath Axes accessor (parent, ancestors, preceding, following, ...).
*
* @return object to perform XPath Axes transformations.
*/
public Dom dom() {
return dom;
}
/**
* Get a conditions object used to verify condition on this element.
*
* @return conditions object
*/
public FluentConditions conditions() {
return conditions;
}
/**
* Build a wait object to wait for a condition of this element.
*
* @return a wait object
*/
public FluentWaitElement await() {
return new FluentWaitElement(control.await(), this);
}
/**
* Execute mouse actions on the element
*
* @return mouse actions object
*/
public MouseElementActions mouse() {
return mouseActions;
}
/**
* Execute keyboard actions on the element
*
* @return keyboard actions object
*/
public KeyboardElementActions keyboard() {
return keyboardActions;
}
/**
* Wrap all underlying elements in a component.
*
* @param componentClass component class
* @param <T> type of component
* @return element as component.
*/
public <T> T as(Class<T> componentClass) {
T component = instantiator.newComponent(componentClass, getElement());
control.injectComponent(component, this, getElement());
return component;
}
/**
* Clear the element
*
* @return fluent web element
*/
public FluentWebElement clear() {
if (!isInputOfTypeFile()) {
webElement.clear();
}
return this;
}
/**
* Clear React input using Backspace only
*
* @return fluent web element
*/
public FluentWebElement clearReactInput() {
if (this.attribute("value").length() != 0) {
javascriptActions.modifyAttribute("value", "");
}
return this;
}
/**
* Submit the element
*
* @return fluent web element
*/
public FluentWebElement submit() {
webElement.submit();
return this;
}
/**
* Set the text element
*
* @param text value to set
* @return fluent web element
*/
public FluentWebElement write(String... text) {
clear();
if (text.length != 0) {
webElement.sendKeys(text[0]);
}
return this;
}
/**
* return the name of the element
*
* @return name of the element
*/
public String name() {
return webElement.getAttribute("name");
}
/**
* return any value of custom attribute (generated=true will return "true" if attribute("generated") is called.
*
* @param name custom attribute name
* @return name value
* @see WebElement#getAttribute(String)
*/
@Nullable
public String attribute(String name) {
return webElement.getAttribute(name);
}
/**
* Get the value of a given CSS property.
*
* @param propertyName the css property name of the element
* @return The current, computed value of the property.
* @see WebElement#getCssValue(String)
*/
public String cssValue(String propertyName) {
return webElement.getCssValue(propertyName);
}
/**
* return the id of the elements
*
* @return id of element
*/
public String id() {
return webElement.getAttribute("id");
}
/**
* return the visible text of the element
*
* @return text of element
* @see WebElement#getText()
*/
public String text() {
return webElement.getText();
}
/**
* return the text content of the element (even invisible through textContent attribute)
*
* @return text content of element
*/
public String textContent() {
return webElement.getAttribute("textContent");
}
/**
* return the value of the elements
*
* @return value of attribute
*/
public String value() {
return webElement.getAttribute("value");
}
/**
* return true if the element is displayed, other way return false
*
* @return boolean value of displayed check
* @see WebElement#isDisplayed()
*/
public boolean displayed() {
boolean displayed;
try {
displayed = webElement.isDisplayed();
} catch (NoSuchElementException | StaleElementReferenceException e) {
displayed = false;
}
return displayed;
}
/**
* return true if the element is enabled, other way return false
*
* @return boolean value of enabled check
* @see WebElement#isEnabled()
*/
public boolean enabled() {
boolean enabled;
try {
enabled = webElement.isEnabled();
} catch (NoSuchElementException | StaleElementReferenceException ex) {
enabled = false;
}
return enabled;
}
/**
* return true if the element is selected, other way false
*
* @return boolean value of selected check
* @see WebElement#isSelected()
*/
public boolean selected() {
boolean selected;
try {
selected = webElement.isSelected();
} catch (NoSuchElementException | StaleElementReferenceException e) {
selected = false;
}
return selected;
}
/**
* Check that this element is visible and enabled such that you can click it.
*
* @return true if the element can be clicked, false otherwise.
*/
public boolean clickable() {
boolean clickable;
try {
clickable = ExpectedConditions.elementToBeClickable(getElement())
.apply(control.getDriver()) != null;
} catch (ElementNotInteractableException | NoSuchElementException | StaleElementReferenceException e) {
clickable = false;
}
return clickable;
}
/**
* Check that this element is no longer attached to the DOM.
*
* @return false is the element is still attached to the DOM, true otherwise.
*/
public boolean stale() {
return ExpectedConditions.stalenessOf(getElement()).apply(control.getDriver());
}
/**
* return the tag name
*
* @return string value of tag name
* @see WebElement#getTagName()
*/
public String tagName() {
return webElement.getTagName();
}
/**
* return the webElement
*
* @return web element
*/
public WebElement getElement() {
return webElement;
}
@Override
public WebElement getWrappedElement() {
return getElement();
}
/**
* return the size of the element
*
* @return dimension/size of element
* @see WebElement#getSize()
*/
public Dimension size() {
return webElement.getSize();
}
/**
* Converts this element as a single element list.
*
* @return list of element
*/
public FluentList<FluentWebElement> asList() {
return instantiator.asComponentList(FluentListImpl.class, FluentWebElement.class, Arrays.asList(webElement));
}
@Override
public FluentList<FluentWebElement> find(By locator, SearchFilter... filters) {
return search.find(locator, filters);
}
@Override
public FluentList<FluentWebElement> find(String selector, SearchFilter... filters) {
return search.find(selector, filters);
}
@Override
public FluentList<FluentWebElement> find(SearchFilter... filters) {
return search.find(filters);
}
@Override
public FluentList<FluentWebElement> find(List<WebElement> rawElements) {
return search.find(rawElements);
}
@Override
public FluentWebElement el(WebElement rawElement) {
return search.el(rawElement);
}
/**
* Get the HTML of a the element
*
* @return the underlying html content
*/
public String html() {
return webElement.getAttribute("innerHTML");
}
@Override
public Fill<FluentWebElement> fill() {
return new Fill<>(this);
}
@Override
public FillSelect<FluentWebElement> fillSelect() {
return new FillSelect<>(this);
}
@Override
public FluentWebElement frame() {
control.window().switchTo().frame(this);
return this;
}
@Override
public Optional<FluentWebElement> optional() {
if (present()) {
return Optional.of(this);
} else {
return Optional.empty();
}
}
/**
* This method return true if the current FluentWebElement is an input of type file
*
* @return boolean value for is input file type
*/
private boolean isInputOfTypeFile() {
return "input".equalsIgnoreCase(tagName()) && "file".equalsIgnoreCase(attribute("type"));
}
/**
* Save actual hook definitions to backup.
*
* @param hookRestoreStack restore stack
*/
/* default */ void setHookRestoreStack(Stack<List<HookDefinition<?>>> hookRestoreStack) {
hookControl.setHookRestoreStack(hookRestoreStack);
}
@Override
public String toString() {
return label.toString();
}
@Override
public <R> R noHook(Class<? extends FluentHook> hook, Function<FluentWebElement, R> function) {
return getHookControl().noHook(hook, function);
}
@Override
public <O, H extends FluentHook<O>> FluentWebElement withHook(Class<H> hook, O options) {
return getHookControl().withHook(hook, options);
}
@Override
public <O, H extends FluentHook<O>> FluentWebElement withHook(Class<H> hook) {
return getHookControl().withHook(hook);
}
@Override
public FluentWebElement noHook(Class<? extends FluentHook>... hooks) {
return getHookControl().noHook(hooks);
}
@Override
public <R> R noHook(Function<FluentWebElement, R> function) {
return getHookControl().noHook(function);
}
@Override
public FluentWebElement noHookInstance(Class<? extends FluentHook>... hooks) {
return getHookControl().noHookInstance(hooks);
}
@Override
public FluentWebElement restoreHooks() {
return getHookControl().restoreHooks();
}
@Override
public FluentWebElement noHookInstance() {
return getHookControl().noHookInstance();
}
@Override
public FluentWebElement noHook() {
return getHookControl().noHook();
}
@Override
public FluentWebElement scrollToCenter() {
return getJavascriptActions().scrollToCenter();
}
@Override
public FluentWebElement scrollIntoView() {
return getJavascriptActions().scrollIntoView();
}
@Override
public FluentWebElement scrollIntoView(boolean alignWithTop) {
return getJavascriptActions().scrollIntoView(alignWithTop);
}
@Override
public FluentWebElement modifyAttribute(String attributeName, String attributeValue) {
return getJavascriptActions().modifyAttribute(attributeName, attributeValue);
}
@Override
public FluentWebElement withLabelHint(String... labelHint) {
return getLabel().withLabelHint(labelHint);
}
@Override
public FluentWebElement withLabel(String label) {
return getLabel().withLabel(label);
}
}