
io.github.kgress.scaffold.webdriver.WebDriverWrapper Maven / Gradle / Ivy
package io.github.kgress.scaffold.webdriver;
import io.github.kgress.scaffold.exception.WebDriverWrapperException;
import io.github.kgress.scaffold.util.AutomationUtils;
import io.github.kgress.scaffold.util.AutomationWait;
import io.github.kgress.scaffold.webelements.AbstractWebElement;
import io.github.kgress.scaffold.webelements.interfaces.BaseWebElement;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.*;
import org.openqa.selenium.WebDriver.Navigation;
import org.openqa.selenium.WebDriver.Options;
import org.openqa.selenium.WebDriver.TargetLocator;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static io.github.kgress.scaffold.util.AutomationUtils.sleep;
/**
* This serves as a buffer between us and Selenium to help guard against drastic changes to their API and functionality
* in the {@link WebDriver}. It mostly delegates to the underlying WebDriver, but in many cases we'll code up special
* behavior for it.
*/
@Slf4j
public class WebDriverWrapper {
private static final Long WINDOW_TIME_OUT_IN_SECONDS = 60L;
private AutomationWait automationWait;
private WebDriver driver;
private long seleniumObjectTimeout = 15;
private LinkedList registeredWindows = new LinkedList<>();
private boolean implicitWaitsEnabled = true; // Flag here for us to determine if implicit waiting is enabled or disabled
/**
* Takes a "real" WebDriver instance and wraps it up in the facade for safe (and thread-safe) handling.
* @param webDriver the root {@link WebDriver}
*/
WebDriverWrapper(WebDriver webDriver) {
this.driver = webDriver;
this.automationWait = new AutomationWait(this);
}
/**
* Find an element on the page
*
* @param by the means in which the element is being found using {@link By}
* @return the element as a {@link WebElement}
*/
public WebElement findElement(By by) {
return driver.findElement(by);
}
/**
* Attempts to locate and return a {@code WebElement} for the specified {@code By} locator off of the parent element passed in
*
* @param element The element being located
* @param by Locator to search by
* @param throwExceptionIfNotFound If true and the element cannot be found, a {@code NoSuchElementException} will be thrown
* if the element cannot be located.
* @return the element as a {@link WebElement}
*/
public WebElement findElement(WebElement element, By by, boolean throwExceptionIfNotFound) {
WebElement returnElement = null;
try {
returnElement = element.findElement(by);
}
// Only need to suppress no such element exceptions -- any other exceptions we run into
// should be treated as unhandled
catch (NoSuchElementException e) {
// Only throw an exception if the caller specifies
if (throwExceptionIfNotFound) {
throw e;
}
}
return returnElement;
}
/**
* Attempts to locate and return a {@code List} for the specified {@code By} locator off of the parent element passed in
*
* @param element The elements being located
* @param by Locator to search by
* @param throwExceptionIfNotFound If true and the element(s) cannot be found, a {@code NoSuchElementException} will be thrown
* if the element cannot be located.
* @return the elements as a {@link List} of {@link WebElement}
*/
public List findElements(WebElement element, By by, boolean throwExceptionIfNotFound) {
List elements = new ArrayList<>();
try {
elements = element.findElements(by);
}
// Only need to suppress no such element exceptions -- any other exceptions we run into
// should be treated as unhandled
catch (NoSuchElementException e) {
// Only throw an exception if the caller specifies
if (throwExceptionIfNotFound) {
throw e;
}
}
return elements;
}
/**
* Returns a list of all the WebElements on the page that match the By locator
*
* @param by the means in which the element is being found using {@link By}
* @return the list of elements as a {@link List} of {@link WebElement}
*/
public List findElements(By by) {
return this.driver.findElements(by);
}
/**
* Returns a list of strongly typed WebElements
*
* @param elementClass the class of the element that is being found
* @param by the method in which the element is being found using {@link By}
* @param the reference to the {@link AbstractWebElement}
* @return the elements as a {@link List} of {@link T}
*/
public List findElements(Class elementClass, By by) {
return this.findElements(elementClass, by, true);
}
/**
* Returns a list of strongly typed WebElements
*
* @param elementClass The class of the element that is being found
* @param by the means in which the element is being found using {@link By}
* @param elementInterface the interface of the element
* @param the reference to the {@link AbstractWebElement}
* @param the reference to the {@link AbstractWebElement}
* @return the elements as a {@link List} of {@link WebElement}
*/
public List findElements(Class elementClass, Class elementInterface, By by) {
if (!elementInterface.isAssignableFrom(elementClass)) {
throw new ClassCastException(String.format("Class %s does not implement interface %s!", elementClass.getSimpleName(), elementInterface.getSimpleName()));
}
List classElements = findElements(elementClass, by);
List interfaceElements = new ArrayList();
for (T classElement : classElements) {
interfaceElements.add((I) classElement);
}
return interfaceElements;
}
/**
* Returns a list of strongly typed WebElements
*
* @param elementClass the class of the element that is being found
* @param by the method in which the element is being found using {@link By}
* @param throwExceptionIfNotFound a {@link Boolean} value of true or false if this error is expected
* @param the reference to the {@link AbstractWebElement}
* @return the elements as a {@link List} of {@link T}
*/
public List findElements(Class elementClass, By by, boolean throwExceptionIfNotFound) {
List elements = new ArrayList<>();
try {
elements = driver.findElements(by);
}
// Only need to suppress no such element exceptions -- any other exceptions we run into
// should be treated as unhandled
catch (NoSuchElementException e) {
// Only throw an exception if the caller specifies
if (throwExceptionIfNotFound) {
throw e;
}
}
List returnElements = new ArrayList<>(elements.size());
// elements should be an empty list if no WebElements were found, and the for/each will never execute, returning an empty list to the caller
for (WebElement element : elements) {
try {
Constructor constructor = elementClass.getConstructor(WebElement.class);
T newElement = constructor.newInstance((WebElement) element);
returnElements.add(newElement);
} catch (Throwable t) {
log.error("Error trying to construct webelement: " + AutomationUtils.getStackTrace(t));
}
}
return returnElements;
}
/**
* Navigate to the URL provided in the parameter
*
* @param url the URL to navigate to
*/
public void get(String url) {
this.driver.get(url);
}
/**
* Returns the JavascriptExecutor for the current WebDriver instance
*
* @return the {@link JavascriptExecutor}
*/
public JavascriptExecutor getJavascriptExecutor() {
if (!(this.driver instanceof JavascriptExecutor)) {
throw new WebDriverWrapperException("Current WebDriver instance does not support JavascriptExecutor");
}
return (JavascriptExecutor) this.driver;
}
/**
* Returns a new Actions object
*
* @return the actions as {@link Actions}
*/
public Actions getActions() {
return new Actions(this.driver);
}
/**
* Adds the specified cookie
*
* @param newCookie Cookie to add
*/
public void addCookie(Cookie newCookie) {
manage().addCookie(newCookie);
}
/**
* Returns the Cookie given by the cookieName parameter. If the cookie does not exist, an Exception will be thrown.
* To get the value of the cookie, invoke the getValue method on the Cookie object.
*
* @param cookieName the name of the cookie
* @return as a {@link Cookie}
* @throws WebDriverWrapperException the exception to throw if the cookie cannot be found
*/
public Cookie getCookie(String cookieName)
throws WebDriverWrapperException {
var cookie = manage().getCookieNamed(cookieName);
if (cookie == null) {
var error = "Could not locate the following cookie: %s";
error = String.format(error, cookieName);
throw new WebDriverWrapperException(error);
}
return cookie;
}
/**
* Deletes the specified cookie. return True if the cookie was deleted successfully, false otherwise
*
* @param oldCookie Cookie to delete
*/
public void deleteCookie(Cookie oldCookie) {
manage().deleteCookie(oldCookie);
}
/**
* Navigate to a specified URL
*
* @return as a {@link Navigation}
*/
public Navigation navigate() {
return this.driver.navigate();
}
/**
* Returns the page source for the current page
*
* @return the page source as a {@link String}
*/
public String getPageSource() {
return this.driver.getPageSource();
}
/**
* Returns the title of the page, as detailed in the DOM.
*
* @return the title of the page as a {@link String}
* @see WebDriver#getTitle()
*/
public String getTitle() {
return this.driver.getTitle();
}
/**
* Return the Current URL the browser is looking at
*
* @return the current URL as a {@link String}
*/
public String getCurrentUrl() {
return this.driver.getCurrentUrl();
}
/**
* Opens a new window with the given URL, then switches context back to it
*
* @param url the url desired to be navigated to in the new window
*/
public void openUrlInNewWindow(String url) {
//First, use javascript to open a new window
getJavascriptExecutor().executeScript("window.open()");
// Secondly, synchronize the windows to account for the popup
this.synchronizeWindows();
// Finally, open the url
this.driver.get(url);
}
/**
* Forces WebDriver to switch to a different window or frame
*
* @return as a {@link TargetLocator}
*/
public TargetLocator switchTo() {
return this.driver.switchTo();
}
/**
* Switches to the specified window by index (e.g. 0 switches to the base (bottom) window)
*
* @param index the index to switch to
*/
public void switchToWindow(int index) {
var window = registeredWindows.get(index);
this.switchToWindow(window);
}
/**
* Switches to the specified window
*
* @param windowHandle the window id
*/
public void switchToWindow(String windowHandle) {
this.driver.switchTo().window(windowHandle);
}
/**
* Switches to the second window that is within the current window handles
*/
public void switchToSecondWindow() {
final var windowsHandles = driver.getWindowHandles();
final var windowHandle = driver.getWindowHandle();
// Switch to the window handle that is not currently focused
for (var handle : windowsHandles) {
if (handle.equals(windowHandle)) {
continue;
}
switchToWindow(handle);
}
}
/**
* Returns a set of window handles which can be used to iterate over all open windows of this WebDriver instance
*
* @return the window handles as a {@link Set} of {@link String}
*/
public Set getWindowHandles() {
return this.driver.getWindowHandles();
}
/**
* Synchronizes the registered windows with the currently open windows. Handles closed and new windows (popups).
*/
public void synchronizeWindows() {
// This custom timeout will only last for the duration of the window handling
var wait = getAutomationWait().setCustomTimeout(WINDOW_TIME_OUT_IN_SECONDS);
wait.waitForCustomCondition(new ExpectedCondition() {
@Override
public Boolean apply(WebDriver input) {
var innerWindows = driver.getWindowHandles();
sleep(200);
// Wait until we know that we have retrieved windows and their size is different than our registered windows
// which equates to a window change of some sort
return (innerWindows != null && innerWindows.size() != registeredWindows.size());
}
@Override
public String toString() {
// Failure message if no window changes are detected
return String.format("window change to happen. %d registered windows present", registeredWindows.size());
}
});
// Once we know we've found a window change, lets settle down for a few seconds and
// freshly retrieve our window handles before attempting any logic
sleep(200);
var windows = driver.getWindowHandles();
// Clear out our registered window handles so we can resynchronize them
registeredWindows.clear();
for (var window : windows) {
registeredWindows.addLast(window);
}
// Now make sure and switch to the last window to be opened
this.switchToWindow(registeredWindows.getLast());
}
/**
* Returns the Selenium timeout for this WebDriver instance
*
* @return the {@link #seleniumObjectTimeout}
*/
public long getSeleniumObjectTimeout() {
return seleniumObjectTimeout;
}
/**
* Sets the Selenium timeout for this WebDriver instance
*
* @param seleniumObjectTimeout the amount of time to set the selenium object timeout
*/
public void setSeleniumObjectTimeout(long seleniumObjectTimeout) {
this.seleniumObjectTimeout = seleniumObjectTimeout;
}
/**
* Takes a screen shot of the current browser state (includes the ENTIRE browser image, including what
* is scrolled off-screen)
*
* @return A base64-encoded String representation of the screen shot
*/
public String getScreenShot() {
if (!TakesScreenshot.class.isAssignableFrom(driver.getClass())) {
throw new WebDriverWrapperException("Driver does not support taking screenshots: " + driver);
}
return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BASE64);
}
/**
* Takes a screen shot of the current browser state and returns it as a File object
*
* @return a File object representation of the screen shot
*/
public File getScreenShotAsFile() {
if (!TakesScreenshot.class.isAssignableFrom(driver.getClass())) {
throw new WebDriverWrapperException("Driver does not support taking screenshots: " + driver);
}
return ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
}
/**
* Returns the window handle of the current window
*
* @return the window handle as a {@link String}
*/
public String getWindowHandle() {
return this.driver.getWindowHandle();
}
/**
* Returns the registered windows
*
* @return the {@link #registeredWindows}
*/
public LinkedList getRegisteredWindows() {
return registeredWindows;
}
/**
* Sets the registered windows
*
* @param registeredWindows the list of required windows
*/
public void setRegisteredWindows(LinkedList registeredWindows) {
this.registeredWindows = registeredWindows;
}
/**
* Gets the scaffold wait time
*
* @return as {@link AutomationWait}
*/
public AutomationWait getAutomationWait() {
return automationWait;
}
/**
* Sets the scaffold wait time
*
* @param automationWait the {@link AutomationWait} required
*/
public void setAutomationWait(AutomationWait automationWait) {
this.automationWait = automationWait;
}
/**
* Checks to see if implicit waits are enabled
*
* @return a true or false on state
*/
public boolean isImplicitWaitsEnabled() {
return implicitWaitsEnabled;
}
/**
* Sets the implicit wait to enabled or disabled
*
* @param implicitWaitsEnabled the state in which the user wishes the implicit wait state
*/
public void setImplicitWaitsEnabled(boolean implicitWaitsEnabled) {
this.implicitWaitsEnabled = implicitWaitsEnabled;
}
/**
* Returns the underlying WebDriver element.
*
* @return as a {@link WebDriver}
*/
public WebDriver getBaseWebDriver() {
return this.driver;
}
/**
* Sets the underlying WebDriver instance for this object
*
* @param webDriver the web driver instance to set
*/
public void setWebDriver(WebDriver webDriver) {
this.driver = webDriver;
}
/**
* Returns the interface used to manage WebDriver properties
*
* @return the manager as {@link Options}
*/
public Options manage() {
return this.driver.manage();
}
/**
* Quits the current WebDriver instance, closing all open windows
*/
public void quit() {
try {
this.driver.quit();
} catch (Exception e) {
log.error("Error quitting driver: " + e);
}
}
/**
* Closes the current window, quitting the current WebDriver instance if it is the only window opened
*/
public void close() {
var synchronizeWindows = false;
// We have to handle switching back to the prior window if dealing with multiple windows
if (this.getWindowHandles().size() > 1) {
synchronizeWindows = true;
}
this.driver.close();
// After closing the window, we can synchronize our windows
if (synchronizeWindows) {
this.synchronizeWindows();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy