/** * Copyright (C) 2013-2015 all@code-story.net * * 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 net.codestory.simplelenium.filters; import net.codestory.simplelenium.DomElement; import net.codestory.simplelenium.Should; import net.codestory.simplelenium.selectors.ByCssSelectorOrByNameOrById; import net.codestory.simplelenium.text.Text; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.Select; import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.*; import java.util.stream.Stream; import static net.codestory.simplelenium.filters.WebElementHelper.text; public class LazyDomElement implements DomElement { private final LazyDomElement parent; private final By selector; private final ElementFilter filter; private final Retry retry; public LazyDomElement(By selector) { this(selector, Retry._30_SECONDS); } public LazyDomElement(By selector, Retry retry) { this(null, selector, ElementFilter.any(), retry); } public LazyDomElement(LazyDomElement parent, By selector) { this(parent, selector, Retry._30_SECONDS); } public LazyDomElement(LazyDomElement parent, By selector, Retry retry) { this(parent, selector, ElementFilter.any(), retry); } private LazyDomElement(LazyDomElement parent, By selector, ElementFilter filter, Retry retry) { this.parent = parent; this.selector = selector; this.filter = filter; this.retry = retry; } // Nested find public DomElement find(String selector) { return new LazyDomElement(this, new ByCssSelectorOrByNameOrById(selector)); } public DomElement find(By selector) { return new LazyDomElement(this, selector); } // Narrow find @Override public ElementFilterBuilder withText() { Function<WebElement, String> toValue = element -> text(element); return with("text", toValue); } @Override public ElementFilterBuilder withId() { Function<WebElement, String> toValue = element -> element.getAttribute("id"); return with("id", toValue); } @Override public ElementFilterBuilder withName() { Function<WebElement, String> toValue = element -> element.getAttribute("name"); return with("name", toValue); } @Override public ElementFilterBuilder withTagName() { Function<WebElement, String> toValue = element -> element.getTagName(); return with("tag name", toValue); } @Override public ElementFilterBuilder withClass() { Function<WebElement, String> toValue = element -> element.getAttribute("class"); return with("class", toValue); } @Override public ElementFilterBuilder with(String name) { Function<WebElement, String> toValue = element -> element.getAttribute(name); return with("attribute[" + name + "]", toValue); } @Override public ElementFilterBuilder withCss(String name) { Function<WebElement, String> toValue = element -> element.getCssValue(name); return with("cssValue[" + name + "]", toValue); } @Override public ElementFilterBuilder with(String description, Function<WebElement, String> toValue) { return new ElementFilterBuilder(this, description, toValue, true); } // Limit results @Override public LazyDomElement first() { return filter("first", StreamFilters.first()); } @Override public LazyDomElement second() { return filter("second", StreamFilters.second()); } @Override public LazyDomElement third() { return filter("third", StreamFilters.third()); } @Override public LazyDomElement nth(int index) { return filter("nth[" + index + "]", StreamFilters.nth(index)); } @Override public LazyDomElement limit(int max) { return filter("limit[" + max + "]", StreamFilters.limit(max)); } @Override public LazyDomElement skip(int count) { return filter("skip[" + count + "]", StreamFilters.skip(count)); } @Override public LazyDomElement last() { return filter("last", StreamFilters.last()); } @Override public LazyDomElement filter(String description, UnaryOperator<Stream<WebElement>> filter) { return with(new ElementFilter(", " + description, filter)); } LazyDomElement with(ElementFilter filter) { return new LazyDomElement(parent, selector, this.filter.and(filter), retry); } // Assertions @Override public Should should() { return new LazyShould(this, Retry._30_SECONDS, true); } // Actions @Override public LazyDomElement fill(CharSequence text) { return execute("fill(" + text + ")", element -> { element.clear(); element.sendKeys(text); }); } @Override public LazyDomElement append(CharSequence text) { return execute("append(" + text + ")", element -> element.sendKeys(text)); } @Override public LazyDomElement pressReturn() { return execute("pressReturn()", element -> element.sendKeys(Keys.RETURN)); } @Override public LazyDomElement pressEnter() { return execute("pressEnter()", element -> element.sendKeys(Keys.ENTER)); } @Override public LazyDomElement sendKeys(CharSequence... keysToSend) { return execute("sendKeys()", element -> element.sendKeys(keysToSend)); } @Override public LazyDomElement clear() { return execute("clear()", element -> element.clear()); } @Override public LazyDomElement submit() { return execute("submit", element -> element.submit()); } @Override public LazyDomElement click() { return execute("click", element -> element.click()); } @Override public LazyDomElement check() { return execute("check", element -> { if (!element.isSelected()) { element.click(); } }); } @Override public LazyDomElement uncheck() { return execute("uncheck", element -> { if (element.isSelected()) { element.click(); } }); } @Override public LazyDomElement click(int x, int y) { return executeActions("click(" + x + "," + y + ")", (element, actions) -> actions.moveToElement(element, x, y).click()); } @Override public LazyDomElement doubleClick() { return executeActions("doubleClick", (element, actions) -> actions.doubleClick(element)); } @Override public LazyDomElement doubleClick(int x, int y) { return executeActions("doubleClick(" + x + "," + y + ")", (element, actions) -> actions.moveToElement(element, x, y).doubleClick()); } @Override public LazyDomElement clickAndHold() { return executeActions("clickAndHold", (element, actions) -> actions.clickAndHold(element)); } @Override public LazyDomElement dragAndDropTo(String destinationSelector) { return executeActions("dragAndDropTo(" + destinationSelector + ")", (element, actions) -> actions .clickAndHold(element) .pause(100) .release(driver().findElementByCssSelector(destinationSelector))); } @Override public LazyDomElement contextClick() { return executeActions("contextClick", (element, actions) -> actions.contextClick(element)); } @Override public LazyDomElement release() { return executeActions("release", (element, actions) -> actions.release(element)); } @Override public LazyDomElement executeActions(String description, BiConsumer<WebElement, Actions> actionsOnElement) { return execute(description, element -> { Actions actions = new Actions(driver()); actionsOnElement.accept(element, actions); actions.build().perform(); }); } // Selection @Override public LazyDomElement select(String text) { return executeSelect("select(" + text + ")", select -> select.selectByVisibleText(text)); } @Override public LazyDomElement deselect() { return executeSelect("deselect()", select -> select.deselectAll()); } @Override public LazyDomElement deselectByValue(String value) { return executeSelect("deselectByValue(" + value + ")", select -> select.deselectByValue(value)); } @Override public LazyDomElement deselectByVisibleText(String text) { return executeSelect("deselectByVisibleText(" + text + ")", select -> select.deselectByVisibleText(text)); } @Override public LazyDomElement deselectByIndex(int index) { return executeSelect("deselectByIndex(" + index + ")", select -> select.deselectByIndex(index)); } @Override public LazyDomElement selectByIndex(int index) { return executeSelect("selectByIndex(" + index + ")", select -> select.selectByIndex(index)); } @Override public LazyDomElement selectByValue(String value) { return executeSelect("selectByValue(" + value + ")", select -> select.selectByValue(value)); } @Override public LazyDomElement executeSelect(String description, Consumer<Select> selectOnElement) { return execute(description, element -> selectOnElement.accept(new Select(element))); } // Actions on low level elements @Override public LazyDomElement execute(Consumer<WebElement> action) { return execute("execute(" + action + ")", action); } // Retry @Override public LazyDomElement retryFor(long duration, TimeUnit timeUnit) { return new LazyDomElement(parent, selector, this.filter.and(filter), new Retry(duration, timeUnit)); } // Internal private LazyDomElement execute(String message, Consumer<WebElement> action) { System.out.println(" - " + Text.toString(selector) + filter.getDescription() + "." + message); Supplier<Optional<WebElement>> findOne = () -> stream().findFirst(); try { retry.execute(findOne, action); } catch (NoSuchElementException e) { throw new AssertionError("Element not found: " + Text.toString(selector)); } // After an action, go back to root level for future finds return new LazyDomElement(parent, selector, filter, retry) { public DomElement find(String selector) { return new LazyDomElement(new ByCssSelectorOrByNameOrById(selector)); } public DomElement find(By selector) { return new LazyDomElement(selector); } }; } @Override public String toString() { return ((parent == null) ? "" : parent.toString() + " ") + Text.toString(selector) + filter.getDescription(); } LazyDomElement parent() { return parent; } Stream<WebElement> stream() { Stream<WebElement> webElements; if (parent != null) { webElements = parent.stream().flatMap(element -> element.findElements(selector).stream()); } else { webElements = driver().findElements(selector).stream(); } return filter.getFilter().apply(webElements); } }