package org.fluentlenium.core.domain; import lombok.experimental.Delegate; import org.fluentlenium.core.FluentControl; import org.fluentlenium.core.action.Fill; import org.fluentlenium.core.action.FillSelect; import org.fluentlenium.core.action.FluentActions; import org.fluentlenium.core.action.FluentJavascriptActionsImpl; import org.fluentlenium.core.action.InputControl; import org.fluentlenium.core.action.KeyboardElementActions; import org.fluentlenium.core.action.MouseElementActions; import org.fluentlenium.core.axes.Axes; import org.fluentlenium.core.components.ComponentInstantiator; import org.fluentlenium.core.conditions.FluentConditions; import org.fluentlenium.core.conditions.WebElementConditions; import org.fluentlenium.core.hook.HookControl; import org.fluentlenium.core.hook.HookControlImpl; import org.fluentlenium.core.hook.HookDefinition; import org.fluentlenium.core.label.FluentLabel; import org.fluentlenium.core.label.FluentLabelImpl; import org.fluentlenium.core.proxy.FluentProxyState; import org.fluentlenium.core.proxy.LocatorHandler; import org.fluentlenium.core.proxy.LocatorProxies; import org.fluentlenium.core.search.Search; import org.fluentlenium.core.search.SearchControl; import org.fluentlenium.core.search.SearchFilter; import org.fluentlenium.core.wait.AwaitControl; import org.fluentlenium.core.wait.FluentWaitElement; import org.fluentlenium.utils.SupplierOfInstance; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.WebElement; import org.openqa.selenium.internal.WrapsElement; import org.openqa.selenium.support.pagefactory.ElementLocator; import org.openqa.selenium.support.ui.ExpectedConditions; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Stack; import java.util.function.Supplier; /** * 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> { private final Search search; private final Axes axes; private final MouseElementActions mouseActions; private final KeyboardElementActions keyboardActions; private final WebElementConditions conditions; private final HookControlImpl<FluentWebElement> hookControl; @Delegate private final FluentLabel<FluentWebElement> label; private final FluentJavascriptActionsImpl<FluentWebElement> javascriptActions; /** * Creates a new fluent web element. * * @param element underlying element * @param control controle 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, new Supplier<FluentWebElement>() { @Override public FluentWebElement get() { LocatorHandler locatorHandler = LocatorProxies.getLocatorHandler(getElement()); ElementLocator locator = locatorHandler.getLocator(); WebElement noHookElement = LocatorProxies.createWebElement(locator); return newComponent(FluentWebElement.this.getClass(), noHookElement); } }); search = new Search(element, this, this.instantiator, this.control); axes = new Axes(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, new Supplier<String>() { @Override public String get() { return getElement().toString(); } }); javascriptActions = new FluentJavascriptActionsImpl<>(this, this.control, new SupplierOfInstance<>(this)); } @Delegate(excludes = {InputControl.class, AwaitControl.class, SearchControl.class}) private FluentControl getFluentControl() { // NOPMD UnusedPrivateMethod return control; } @Delegate private HookControl<FluentWebElement> getHookControl() { // NOPMD UnusedPrivateMethod return hookControl; } @Delegate private FluentJavascriptActionsImpl<FluentWebElement> getJavascriptActions() { //NOPMD UnusedPrivateMethod return javascriptActions; } @Override public FluentWebElement click() { webElement.click(); return this; } @Override public FluentWebElement doubleClick() { mouse().doubleClick(); return this; } @Override public FluentWebElement contextClick() { mouseActions.contextClick(); 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. */ public Axes axes() { return axes; } /** * 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 componen.. * * @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()); injectComponent(component, this, getElement()); return component; } /** * Clear the element * * @return fluent web element */ public FluentWebElement clear() { if (!isInputOfTypeFile()) { webElement.clear(); } 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) */ 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 */ public boolean displayed() { return webElement.isDisplayed(); } /** * return true if the element is enabled, other way return false * * @return boolean value of enabled check * @see WebElement#isEnabled() */ public boolean enabled() { return webElement.isEnabled(); } /** * return true if the element is selected, other way false * * @return boolean value of selected check * @see WebElement#isSelected() */ public boolean selected() { return webElement.isSelected(); } /** * 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() { return ExpectedConditions.elementToBeClickable(getElement()).apply(control.getDriver()) != null; } /** * 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 */ 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> $(String selector, SearchFilter... filters) { return find(selector, filters); } @Override public FluentWebElement el(String selector, SearchFilter... filters) { return find(selector, filters).first(); } @Override public FluentList<FluentWebElement> $(SearchFilter... filters) { return find(filters); } @Override public FluentWebElement el(SearchFilter... filters) { return find(filters).first(); } @Override public FluentList<FluentWebElement> $(By locator, SearchFilter... filters) { return find(locator, filters); } @Override public FluentWebElement el(By locator, SearchFilter... filters) { return find(locator, filters).first(); } @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 FluentList<FluentWebElement> $(List<WebElement> rawElements) { return search.$(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 fill() { return new Fill(this); } @Override public FillSelect fillSelect() { return new FillSelect(this); } @Override public FluentWebElement frame() { 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(); } }