/* Copyright 2014 Red Hat, Inc. and/or its affiliates. This file is part of darcy-webdriver. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.redhat.darcy.webdriver; import static com.redhat.synq.Synq.after; import com.redhat.darcy.ui.DarcyException; import com.redhat.darcy.ui.FindableNotPresentException; import com.redhat.darcy.ui.api.Locator; import com.redhat.darcy.ui.api.Transition; import com.redhat.darcy.ui.api.View; import com.redhat.darcy.ui.api.elements.Element; import com.redhat.darcy.ui.internal.SimpleTransition; import com.redhat.darcy.web.api.Alert; import com.redhat.darcy.web.api.Browser; import com.redhat.darcy.web.api.CookieManager; import com.redhat.darcy.web.api.Frame; import com.redhat.darcy.web.api.ViewUrl; import com.redhat.darcy.web.api.WebSelection; import com.redhat.darcy.webdriver.internal.DelegatingWebContext; import com.redhat.darcy.webdriver.internal.TargetedWebDriver; import com.redhat.darcy.webdriver.internal.WebDriverCookieManager; import com.redhat.darcy.webdriver.internal.WebDriverWebContext; import com.redhat.darcy.webdriver.internal.WebDriverWebSelection; import com.redhat.darcy.webdriver.internal.WrapsTargetedDriver; import com.redhat.synq.Event; import org.hamcrest.Matcher; import org.openqa.selenium.NoSuchFrameException; import org.openqa.selenium.NoSuchSessionException; import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.OutputType; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.Objects; import java.util.function.Supplier; /** * The main wrapper around a {@link org.openqa.selenium.WebDriver} in order to implement {@link * com.redhat.darcy.web.api.Browser}. This class also implements {@link com.redhat.darcy.web.api.Frame}, * which is a subset of the Browser API. * <p> * There is one key difference between a Browser in Darcy and a WebDriver in Selenium. In Darcy, a * Browser is one:one with a specific window/tab or frame. In WebDriver, a single WebDriver * connection may manage many resulting windows or frames. It is assumed that the WebDriver passed * to this class is pointed at a specific target. * <p> * Implementation of {@link com.redhat.darcy.web.api.Browser} is straightforward, however, in addition * to forwarding calls to the relevant WebDriver method, we will use our page object structure to * wait for those page objects to load as is required by implementers. * * @see com.redhat.darcy.webdriver.internal.TargetedWebDriver */ public class WebDriverBrowser implements Browser, Frame, WebDriverWebContext, WrapsTargetedDriver { private final TargetedWebDriver driver; private final WebDriverWebContext webContext; /** * @param driver A WebDriver implementation to wrap, pointed at some target (like a specific * frame or window), in order to control a browser window or frame. * @param webContext A WebContext that represents the driver in order to find elements and other * contexts. This class implements WebContext by forwarding to this * implementation. */ public WebDriverBrowser(TargetedWebDriver driver, WebDriverWebContext webContext) { this.driver = Objects.requireNonNull(driver, "driver"); this.webContext = Objects.requireNonNull(webContext, "webContext"); } /** * @param driver A WebDriver implementation to wrap, pointed at some target (like a specific * frame or window). * @param parentContext A parent context that can find other contexts (windows, frames). This * class implements ParentContext by forwarding to this implementation. The * parent context must be scoped to the same target as {@code driver}. * @param elementContext An element context that can find other elements. This class implements * ElementContext by forwarding to this implementation. The element * context must be scoped to the same target as {@code driver}. */ public WebDriverBrowser(TargetedWebDriver driver, WebDriverParentContext parentContext, WebDriverElementContext elementContext) { this(driver, new DelegatingWebContext(elementContext, parentContext)); } @Override public boolean isPresent() { return driver.isPresent(); } @Override public <T extends View> Event<T> open(ViewUrl<T> viewUrl) { Objects.requireNonNull(viewUrl); return open(viewUrl.url(), viewUrl.destination()); } @Override public <T extends View> Event<T> open(String url, T destination) { Objects.requireNonNull(url); Objects.requireNonNull(destination); return after(() -> attempt(() -> driver.get(url))) .expect(transition().to(destination)); } @Override public String getCurrentUrl() { return attemptAndGet(driver::getCurrentUrl); } @Override public String getTitle() { return attemptAndGet(driver::getTitle); } @Override public String getSource() { return attemptAndGet(driver::getPageSource); } @Override public <T extends View> Event<T> back(T destination) { Objects.requireNonNull(destination); return after(() -> attempt(() -> driver.navigate().back())) .expect(transition().to(destination)); } @Override public <T extends View> Event<T> forward(T destination) { Objects.requireNonNull(destination); return after(() -> attempt(() -> driver.navigate().forward())) .expect(transition().to(destination)); } @Override public <T extends View> Event<T> refresh(T destination) { Objects.requireNonNull(destination); return after(() -> attempt(() -> driver.navigate().refresh())) .expect(transition().to(destination)); } @Override public CookieManager cookies() { return new WebDriverCookieManager(driver); } @Override public void close() { attempt(driver::close); } @Override public void closeAll() { // TODO: Any harm in calling this with no windows open? driver.quit(); } @Override public void takeScreenshot(OutputStream outputStream) { try (OutputStream stream = outputStream) { byte[] data = attemptAndGet(() -> driver.getScreenshotAs(OutputType.BYTES)); stream.write(data); stream.flush(); } catch (IOException e) { throw new DarcyException("Could not take screenshot", e); } } @Override public WebSelection find() { return new WebDriverWebSelection(this); } @Override public Transition transition() { return new SimpleTransition(this); } @Override public Alert alert() { return webContext.alert(); } @Override public <T> List<T> findAllById(Class<T> type, String id) { return attemptAndGet(() -> webContext.findAllById(type, id)); } @Override public <T> List<T> findAllByName(Class<T> type, String name) { return attemptAndGet(() -> webContext.findAllByName(type, name)); } @Override public <T> List<T> findAllByXPath(Class<T> type, String xpath) { return attemptAndGet(() -> webContext.findAllByXPath(type, xpath)); } @Override public <T> List<T> findAllByChained(Class<T> type, Locator... locators) { return attemptAndGet(() -> webContext.findAllByChained(type, locators)); } @Override public <T> List<T> findAllByLinkText(Class<T> type, String linkText) { return attemptAndGet(() -> webContext.findAllByLinkText(type, linkText)); } @Override public <T> List<T> findAllByTextContent(Class<T> type, String textContent) { return attemptAndGet(() -> webContext.findAllByTextContent(type, textContent)); } @Override public <T> List<T> findAllByPartialTextContent(Class<T> type, String partialTextContent) { return attemptAndGet(() -> webContext.findAllByPartialTextContent(type, partialTextContent)); } @Override public <T> List<T> findAllByNested(Class<T> type, Element parent, Locator child) { return attemptAndGet(() -> webContext.findAllByNested(type, parent, child)); } @Override public <T> T findById(Class<T> type, String id) { return attemptAndGet(() -> webContext.findById(type, id)); } @Override public <T> List<T> findAllByHtmlTag(Class<T> type, String tag) { return attemptAndGet(() -> webContext.findAllByHtmlTag(type, tag)); } @Override public <T> List<T> findAllByCss(Class<T> type, String css) { return attemptAndGet(() -> webContext.findAllByCss(type, css)); } @Override public <T> List<T> findAllByClassName(Class<T> type, String className) { return attemptAndGet(() -> webContext.findAllByClassName(type, className)); } @Override public <T> T findByName(Class<T> type, String name) { return attemptAndGet(() -> webContext.findByName(type, name)); } @Override public <T> T findByXPath(Class<T> type, String xpath) { return attemptAndGet(() -> webContext.findByXPath(type, xpath)); } @Override public <T> T findByLinkText(Class<T> type, String linkText) { return attemptAndGet(() -> webContext.findByLinkText(type, linkText)); } @Override public <T> T findByChained(Class<T> type, Locator... locators) { return attemptAndGet(() -> webContext.findByChained(type, locators)); } @Override public <T> T findByTextContent(Class<T> type, String textContent) { return attemptAndGet(() -> webContext.findByTextContent(type, textContent)); } @Override public <T> T findByPartialTextContent(Class<T> type, String partialTextContent) { return attemptAndGet(() -> webContext.findByPartialTextContent(type, partialTextContent)); } @Override public <T> T findByHtmlTag(Class<T> type, String tag) { return attemptAndGet(() -> webContext.findByHtmlTag(type, tag)); } @Override public <T> T findByCss(Class<T> type, String css) { return attemptAndGet(() -> webContext.findByCss(type, css)); } @Override public <T> T findByClassName(Class<T> type, String className) { return attemptAndGet(() -> webContext.findByClassName(type, className)); } @Override public <T> T findByNested(Class<T> type, Element parent, Locator child) { return attemptAndGet(() -> webContext.findByNested(type, parent, child)); } @Override public <T> List<T> findAllByAttribute(Class<T> type, String attribute, String value) { return attemptAndGet(() -> webContext.findAllByAttribute(type, attribute, value)); } @Override public <T> T findByAttribute(Class<T> type, String attribute, String value) { return attemptAndGet(() -> webContext.findByAttribute(type, attribute, value)); } @Override public <T> List<T> findAllByView(Class<T> type, View view) { return attemptAndGet(() -> webContext.findAllByView(type, view)); } @Override public <T> T findByView(Class<T> type, View view) { return attemptAndGet(() -> webContext.findByView(type, view)); } @Override public <T> List<T> findAllByTitle(Class<T> type, String title) { return attemptAndGet(() -> webContext.findAllByTitle(type, title)); } @Override public <T> T findByTitle(Class<T> type, String title) { return attemptAndGet(() -> webContext.findByTitle(type, title)); } @Override public <T> List<T> findAllByUrl(Class<T> type, Matcher<? super String> urlMatcher) { return attemptAndGet(() -> webContext.findAllByUrl(type, urlMatcher)); } @Override public <T> T findByUrl(Class<T> type, Matcher<? super String> urlMatcher) { return attemptAndGet(() -> webContext.findByUrl(type, urlMatcher)); } @Override public TargetedWebDriver getWrappedDriver() { return driver; } /** * Wrapper for interacting with a targeted driver that may or may not actually be present. */ private void attempt(Runnable action) { try { action.run(); } catch (NoSuchFrameException | NoSuchWindowException | NoSuchSessionException e) { throw new FindableNotPresentException(this, e); } } /** * Wrapper for interacting with a targeted driver that may or may not actually be present. * Returns a result. */ private <T> T attemptAndGet(Supplier<T> action) { try { return action.get(); } catch (NoSuchFrameException | NoSuchWindowException | NoSuchSessionException e) { throw new FindableNotPresentException(this, e); } } }