org.testeditor.fixture.web.WebDriverFixture.java Source code

Java tutorial

Introduction

Here is the source code for org.testeditor.fixture.web.WebDriverFixture.java

Source

/*******************************************************************************
 * Copyright (c) 2012 - 2018 Signal Iduna Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Signal Iduna Corporation - initial API and implementation
 * akquinet AG
 * itemis AG
 *******************************************************************************/

package org.testeditor.fixture.web;

import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.ie.InternetExplorerOptions;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.BrowserType;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.UnexpectedTagNameException;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.testeditor.fixture.core.FixtureException;
import org.testeditor.fixture.core.MaskingString;
import org.testeditor.fixture.core.TestRunListener;
import org.testeditor.fixture.core.TestRunReportable;
import org.testeditor.fixture.core.TestRunReporter;
import org.testeditor.fixture.core.TestRunReporter.Action;
import org.testeditor.fixture.core.TestRunReporter.SemanticUnit;
import org.testeditor.fixture.core.TestRunReporter.Status;
import org.testeditor.fixture.core.artifacts.TestArtifact;
import org.testeditor.fixture.core.artifacts.TestArtifactRegistry;
import org.testeditor.fixture.core.interaction.FixtureMethod;
import org.testeditor.fixture.web.json.BrowserSetting;
import org.testeditor.fixture.web.json.BrowserSettingsManager;
import org.testeditor.fixture.web.json.BrowserSetupElement;

import com.google.gson.JsonObject;

import io.github.bonigarcia.wdm.ChromeDriverManager;
import io.github.bonigarcia.wdm.DriverManagerType;
import io.github.bonigarcia.wdm.FirefoxDriverManager;
import io.github.bonigarcia.wdm.InternetExplorerDriverManager;
import io.github.bonigarcia.wdm.WebDriverManager;

/**
 * The {@code WebDriverFixture} class represents methods for automating
 * browser-actions like clicking on gui-widgets by means of a test-driver called
 * {@code WebDriver}.
 */
public class WebDriverFixture implements TestRunListener, TestRunReportable {

    // Proxy keys
    public static final String HTTP_PROXY_HOST = "http_proxyHost";
    public static final String HTTP_PROXY_PASSWORD = "http_proxyPassword";
    public static final String HTTP_PROXY_USER = "http_proxyUser";

    // Proxy values
    private static String httpProxyHost;
    private static String httpProxyPassword;
    private static String httpProxyUser;

    private WebDriver driver;
    private static final Logger logger = LoggerFactory.getLogger(WebDriverFixture.class);
    Marker fatal = MarkerFactory.getMarker("FATAL");

    private String executeScript = null;

    private final Supplier<TestArtifactRegistry> testArtifactRegistrySupplier;

    public WebDriverFixture() {
        this(TestArtifactRegistry::getInstance);
    }

    public WebDriverFixture(Supplier<TestArtifactRegistry> testArtifactRegistry) {
        this.testArtifactRegistrySupplier = testArtifactRegistry;
    }

    /**
     * specifies the time to wait (in seconds) before an action will be performed.
     * 
     * @param timeToWait
     * @throws InterruptedException
     */
    @FixtureMethod
    public void waitSeconds(long timeToWait) throws FixtureException {
        wrappedSleep(timeToWait * 1000, "wait interrupted", FixtureException.keyValues());
    }

    /**
     * @return {@code WebDriver} to perform actions.
     */
    public WebDriver getDriver() {
        return driver;
    }

    /**
     * sets the {@code WebDriver}
     * 
     * @param driver
     */
    @FixtureMethod
    public void setDriver(WebDriver driver) throws FixtureException {
        if (driver == null) {
            logger.error(fatal, "WebDriver is null");
            throw new FixtureException("WebDriver cannot be null", new IllegalArgumentException());
        } else {
            this.driver = driver;
            logger.trace("WebDriver is set");
        }
    }

    /**
     * Sets a String as a script-name for execution.
     * 
     * @param executionScript
     */
    @FixtureMethod
    public void setExecuteScript(String executionScript) throws FixtureException {
        executeScript = executionScript;
        logger.trace("Execution script is set - name = {}", executionScript);
    }

    /**
     * Starts a specified browser. Following Strings are available:<br>
     * 
     * <ul>
     * <li><b>default</b> -opens a specific browser (On Windows -> Internet
     * Explorer, on others -> Firefox), but Firefox will only work for versions >
     * 55.0.3</li>
     * <li><b>firefox</b> - starts a locally installed firefox instance with
     * {@code GeckoDriver}</li>
     * <li><b>ie</b> - opens Microsoft Windows Internet Explorer</li>
     * <li><b>chrome</b> - opens Google Chrome</li>
     * </ul>
     * 
     * @param browser String literal for used browser
     * @return {@code WebDriver}
     */
    @FixtureMethod
    public WebDriver startBrowser(String browser) throws FixtureException {
        logger.debug("Starting browser: {}", browser);
        switch (browser) {
        case "default":
            if (SystemUtils.IS_OS_WINDOWS) {
                launchInternetExplorer();
            } else {
                launchFirefox();
            }
            break;
        case "firefox":
            launchFirefox();
            break;
        case "ie":
            launchInternetExplorer();
            break;
        case "chrome":
            launchChrome();
            break;
        default:
            throw new FixtureException("Unknown browser.", FixtureException.keyValues("browser", browser));
        }
        configureDriver();
        return driver;
    }

    @Override
    public void initWithReporter(TestRunReporter reporter) {
        reporter.addListener(this);
    }

    @Override
    public void reported(SemanticUnit unit, Action action, String msg, String id, Status status,
            Map<String, String> variables) {
        if (unit == SemanticUnit.TEST && action == Action.ENTER) {
            runningTest = msg;
        }
        if (screenshotShouldBeMade(unit, action, msg)) {
            String screenshotFileName = makeScreenshot(msg + '.' + id);
            testArtifactRegistrySupplier.get().register(new TestArtifact("screenshot", screenshotFileName), id);
        }
    }

    @Override
    public void reportAssertionExit(AssertionError e) {
        makeScreenshot("ASSERTION-ERROR");
    }

    @Override
    public void reportExceptionExit(Exception e) {
        makeScreenshot("EXCEPTION");
    }

    @Override
    public void reportFixtureExit(FixtureException e) {
        makeScreenshot("FIXTURE-EXCEPTION");
    }

    private String runningTest = null;
    private static final int SCREENSHOT_FILENAME_MAXLEN = 128;

    private String getCurrentTestCase() {
        return runningTest != null ? runningTest : "UNKNOWN_TEST";
    }

    private String getScreenshotPath() {
        // configurable through maven build?
        return "screenshots";
    }

    private boolean screenshotShouldBeMade(SemanticUnit unit, Action action, String msg) {
        // configurable through maven build?
        return (((action == Action.LEAVE) && unit == SemanticUnit.STEP) || unit == SemanticUnit.TEST)
                && driver != null;
    }

    private String reduceToMaxLen(String base, int maxLen) {
        logger.trace("BaseFilename : {} should have the maximal length of: {} characters.", base, maxLen);
        if (base.length() < maxLen) {
            logger.trace("BaseFileName used with maximal length of {} characters: {}", maxLen, base);
            return base;
        } else {
            String shortedString = base.substring(0, maxLen);
            logger.trace("Shortened baseFileName to maximal lenght of {} characters : {}", maxLen, base);
            return shortedString;
        }
    }

    private String constructScreenshotFilename(String filenameBase, String testcase) {
        String additionalGraphicType = ".png";
        String escapedBaseName = filenameBase.replaceAll("[^a-zA-Z0-9.-]", "_").replaceAll("_+", "_")
                .replaceAll("_+\\.", ".").replaceAll("\\._+", ".");
        String timeStr = new SimpleDateFormat("HHmmss.SSS").format(new Date());
        String dateStr = new SimpleDateFormat("YYYYMMdd").format(new Date());
        StringBuffer finalFilenameBuffer = new StringBuffer();
        int lenOfFixedElements = timeStr.length() + additionalGraphicType.length() + 1/* hyphen */;
        finalFilenameBuffer //
                .append(getScreenshotPath()) //
                .append('/').append(reduceToMaxLen(testcase, SCREENSHOT_FILENAME_MAXLEN))//
                .append('/').append(dateStr) //
                .append('/').append(timeStr).append('-') //
                .append(reduceToMaxLen(escapedBaseName, SCREENSHOT_FILENAME_MAXLEN - lenOfFixedElements))//
                .append(additionalGraphicType);
        String fileName = finalFilenameBuffer.toString();
        logger.trace("Screenshot fileName: {}", fileName);
        return fileName;
    }

    /**
     * Write a screenshot of the current ui into a file, based on the basic
     * filenameBase provided. The final filename is constructed using the testcase a
     * hash of the fixture itself and a shortened timestamp.
     * 
     * @param filenameBase user definable part of the final filename
     */
    @FixtureMethod
    public String screenshot(String filenameBase) throws FixtureException {
        return makeScreenshot(filenameBase);
    }

    protected String makeScreenshot(String filenameBase) {
        String testcase = getCurrentTestCase();
        String finalFilename = constructScreenshotFilename(filenameBase, testcase);
        if (driver != null) {
            try {
                File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
                FileUtils.copyFile(scrFile, new File(finalFilename));
                logger.debug("Wrote screenshot to file='{}'.", finalFilename);
            } catch (IOException e) {
                logger.error("Could not write screenshot to file='{}'.", finalFilename);
            }
        } else {
            logger.warn("Driver not set (yet). Could not write screenshot to file='{}'.", finalFilename);
        }
        return finalFilename;
    }

    /**
     * starts Firefox Portable. This works for Versions < 47.0. preferably Firefox
     * ESR 45.4.0
     * 
     * @param browserPath
     * @return {@code WebDriver}
     */
    @FixtureMethod
    public WebDriver startFireFoxPortable(String browserPath) throws FixtureException {
        logger.debug("Starting Firefox Portable on path : {} ...", browserPath);
        setupDrivermanager(FirefoxDriverManager.getInstance(DriverManagerType.FIREFOX));
        logger.trace("WebDriverManager setup executed!");
        FirefoxBinary binary = new FirefoxBinary(new File(browserPath));
        FirefoxOptions options = new FirefoxOptions();
        options.setBinary(binary);
        FirefoxDriver firefoxdriver = new FirefoxDriver(options);
        driver = firefoxdriver;
        logger.trace("Firefox portable setup executed!");
        logBrowserVersion(options, firefoxdriver);
        return driver;
    }

    /**
     * launches Google Chrome with the ChromeDriverManager, this will be downloaded
     * automatically and will be saved in the directory
     * /~USER_HOME/m2/repository/webdriver/ in every according OS.
     * 
     * @throws FixtureException
     */
    private void launchChrome() throws FixtureException {
        logger.debug("Starting Google Chrome ...");
        setupDrivermanager(ChromeDriverManager.getInstance(DriverManagerType.CHROME));
        logger.trace("WebDriverManager setup executed!");
        ChromeOptions options = populateBrowserSettingsForChrome();
        ChromeDriver chromeDriver = new ChromeDriver(options);
        driver = chromeDriver;
        logger.trace("Google Chrome setup executed!");
        logBrowserVersion(options, chromeDriver);
        registerShutdownHook(driver);
    }

    /**
     * launches Firefox with FirefoxDriverManager, this will be downloaded
     * automatically and will be saved in the directory
     * /~USER_HOME/m2/repository/webdriver/ in every according OS.
     * 
     * @throws FixtureException
     */
    private void launchFirefox() throws FixtureException {
        logger.debug("Starting Firefox ...");
        setupDrivermanager(FirefoxDriverManager.getInstance(DriverManagerType.FIREFOX));
        logger.trace("WebDriverManager setup executed succesfully!");
        FirefoxOptions options = populateBrowserSettingsForFirefox();
        FirefoxDriver firefoxdriver = new FirefoxDriver(options);
        driver = firefoxdriver;
        logger.trace("Firefox setup executed!");
        logBrowserVersion(options, firefoxdriver);
        registerShutdownHook(driver);
    }

    /**
     * launches Windows Internet Explorer with IEDriverServerManager, this will be
     * downloaded automatically and will be saved in the directory
     * /~USER_HOME/m2/repository/webdriver/ in every according OS.
     * 
     * @throws FixtureException
     */
    private void launchInternetExplorer() throws FixtureException {
        logger.debug("Starting Internet Explorer ...");
        setupDrivermanager(InternetExplorerDriverManager.getInstance(DriverManagerType.IEXPLORER));
        logger.trace("WebDriverManager setup executed!");
        InternetExplorerOptions options = populateBrowserSettingsForInternetExplorer();
        InternetExplorerDriver iedriver = new InternetExplorerDriver(options);
        driver = iedriver;
        logger.trace("Internet Explorer setup executed!");
        logBrowserVersion(options, iedriver);
        registerShutdownHook(driver);
    }

    private void logBrowserVersion(Capabilities capabilities, RemoteWebDriver remoteDriver) {
        String browserName = capabilities.getBrowserName();
        String version = remoteDriver.getCapabilities().getVersion();
        logger.debug("Browser \"{}\" has version \"{}\".", browserName, version);
    }

    private ChromeOptions populateBrowserSettingsForChrome() throws FixtureException {
        List<BrowserSetting> options = new ArrayList<>();
        // specifying capabilities and options with the aid of the browser type.
        populateWithBrowserSpecificSettings(BrowserType.CHROME, options);
        ChromeOptions chromeOptions = new ChromeOptions();
        // Specific method because a ChromeOption is just a String like
        // "allow-outdated-plugins" or "load-extension=/path/to/unpacked_extension"
        populateChromeOption(options, chromeOptions);
        return chromeOptions;
    }

    private FirefoxOptions populateBrowserSettingsForFirefox() throws FixtureException {
        List<BrowserSetting> options = new ArrayList<>();
        // specifying capabilities and options with the aid of the browser type.
        populateWithBrowserSpecificSettings(BrowserType.FIREFOX, options);
        FirefoxOptions firefoxOptions = new FirefoxOptions();
        // Specific method because Firefox Options consists of key value pairs
        // with different data types.
        populateFirefoxOption(options, firefoxOptions);
        return firefoxOptions;
    }

    private InternetExplorerOptions populateBrowserSettingsForInternetExplorer() throws FixtureException {
        List<BrowserSetting> options = new ArrayList<>();
        // specifying capabilities and options with the aid of the browser type.
        populateWithBrowserSpecificSettings(BrowserType.IE, options);
        // Specific method because an InternetExplorerOption is just a key value like
        // "ignoreProtectedModeSettings = true" or "requireWindowFocus = true"
        InternetExplorerOptions ieOptions = new InternetExplorerOptions();
        populateIeOptions(options, ieOptions);
        return ieOptions;
    }

    private void populateChromeOption(List<BrowserSetting> options, ChromeOptions chromeOptions) {
        StringBuffer buffer = new StringBuffer();
        if (options != null) {
            options.forEach((option) -> {
                buffer.append(" " + (String) option.getValue() + " ");
                chromeOptions.addArguments((String) option.getValue());
            });
        }
        logger.trace("All Chrome options: {}", buffer.toString());
    }

    private void populateIeOptions(List<BrowserSetting> options, InternetExplorerOptions ieOptions) {
        StringBuffer buffer = new StringBuffer();
        if (options != null) {
            options.forEach((option) -> {
                buffer.append(" key:" + option.getKey() + " value:" + (String) option.getValue() + " ");
                ieOptions.setCapability(option.getKey(), option.getValue());
            });
        }
        logger.trace("All Internet Explorer options: {}", buffer.toString());
    }

    private void populateFirefoxOption(List<BrowserSetting> options, FirefoxOptions firefoxOptions) {
        StringBuffer buffer = new StringBuffer();
        if (options != null) {
            options.forEach((option) -> {
                Object value = option.getValue();
                switch (value.getClass().getSimpleName()) {
                case "String":
                    buffer.append(" key:" + option.getKey() + " value:" + (String) option.getValue() + " ");
                    firefoxOptions.addPreference(option.getKey(), (String) option.getValue());
                    break;

                case "Integer":
                    buffer.append(" key:" + option.getKey() + " value:" + (Integer) option.getValue() + " ");
                    firefoxOptions.addPreference(option.getKey(), (Integer) option.getValue());
                    break;

                case "Boolean":
                    buffer.append(" key:" + option.getKey() + " value:" + (Boolean) option.getValue() + " ");
                    firefoxOptions.addPreference(option.getKey(), (Boolean) option.getValue());
                    break;

                default:
                    logger.error("Only Strings, Integer or Boolean values are allowed for Option values.");
                    throw new RuntimeException(
                            "Only Strings, Integer or Boolean values are allowed for Option values but was: "
                                    + value.getClass().getSimpleName());

                }

            }

            );
        }
        logger.trace("All Firefox options: {}", buffer.toString());
    }

    private void populateWithBrowserSpecificSettings(String browserName, List<BrowserSetting> options)
            throws FixtureException {
        StringBuilder builder = new StringBuilder();
        BrowserSettingsManager manager = new BrowserSettingsManager();
        List<BrowserSetupElement> browserSettings = manager.getBrowserSettings();
        browserSettings.forEach((setting) -> {
            if (setting.getBrowserName().equalsIgnoreCase(browserName)) {
                List<BrowserSetting> separateOptions = setting.getOptions();
                separateOptions.forEach(option -> {
                    builder.append(String.format("key: %23s   -   value: %s", option.getKey(), option.getValue()));
                    builder.append("\n");

                });
                options.addAll(separateOptions);
            }
        });
        logger.trace("\n********************************************************\n"
                + "Browserspecific settings: \n{}" + "********************************************************\n",
                builder.toString());
    }

    private void setupDrivermanager(WebDriverManager manager) {
        if (areProxyCredentialsAvailable()) {
            settingProxyCredentials(manager);
        }
        manager.setup();
    }

    private boolean areProxyCredentialsAvailable() {
        String proxyHost = System.getenv(HTTP_PROXY_HOST);
        boolean proxyCredentialsAvailable = false;
        if (proxyHost != null && !proxyHost.isEmpty()) {
            httpProxyHost = proxyHost;
            httpProxyUser = System.getenv(HTTP_PROXY_USER);
            httpProxyPassword = System.getenv(HTTP_PROXY_PASSWORD);
            proxyCredentialsAvailable = true;
            logger.debug("Proxy settings found as System Environment Variable: {}", HTTP_PROXY_HOST);
            logger.trace("Proxy user and password set as System Environment Variables : {} and {}", HTTP_PROXY_USER,
                    HTTP_PROXY_PASSWORD);
        } else {
            logger.trace(
                    "No proxy set - No settings found as System Environment Variable: {}. "
                            + "If proxy settings are neccessary, following Environment Variables "
                            + "have to be specified for test execution : {} = <YOUR_PROXY_HOSTNAME> ; "
                            + "{} = <YOUR_PROXY_USERNAME> ; {} = <YOUR_PROXYPASSWORD>",
                    HTTP_PROXY_HOST, HTTP_PROXY_HOST, HTTP_PROXY_USER, HTTP_PROXY_PASSWORD);
        }
        return proxyCredentialsAvailable;
    }

    /**
     * To download the correct version of the WebDriver-Server for each browser
     * manufacturer for using W3C WebDriver-compatible clients to interact with, we
     * need proxy credentials if "System under Test" is located behind a proxy.
     * Because of this reason there is a pleasant way to download the corresponding
     * WebDriver-Server with the help of the <a href=
     * "https://github.com/bonigarcia/webdrivermanager">WebDriverManager</a>
     * developed by Boni Garcia, which provides an ability performing a download in
     * an automated way. The proxy credentials should be provided as "System
     * Environment Variables" before the execution of tests.
     * <p>
     * 
     * There are 3 variables to be provided so far.<br>
     * 
     * Here is an example for naming the proxy variables e.g. on a linux system<br>
     * 
     * <pre>
     * 1.)  export http.proxyHost     = my.proxy.url
     * 2.)  export http.proxyPassword = myProxyPassword
     * 3.)  export http.proxyUser     = myProxyUser
     * </pre>
     * 
     * @param browserManager The specific BrowserManager of a browser.
     */
    private void settingProxyCredentials(WebDriverManager browserManager) {
        logger.debug("Proxy Credentials for WebDrivermanager ( {} = {} ; {} = {})", HTTP_PROXY_HOST, httpProxyHost,
                HTTP_PROXY_USER, httpProxyUser);
        browserManager.proxy(httpProxyHost);
        browserManager.proxyUser(httpProxyUser);
        browserManager.proxyPass(httpProxyPassword);
    }

    /**
     * ShutdownHook for teardown of started Browsermanager
     * 
     * @param driver Webdriver to be used
     */
    private void registerShutdownHook(final WebDriver driver) {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                driver.quit();
            }
        });
        logger.debug("ShutDownHook registered on WebDriver.");
    }

    /**
     * Specifies behavior of Browser. Prerequisite for a test to be executed is,
     * that browsers are opened maximized to prevent failures.
     */
    protected void configureDriver() {
        logger.trace("Setting Implicit Wait 10 seconds");
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        // because executing tests results in problems on travis build
        DisplayMode displayMode = getDisplayMode();
        int width = displayMode.getWidth();
        int height = displayMode.getHeight();
        logger.debug("Screen-Width: " + width + " Screen-Height: " + height);
        driver.manage().window().setSize(new Dimension(width, height));
    }

    private DisplayMode getDisplayMode() {
        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        return gd.getDisplayMode();
    }

    /**
     * opens a specified URL in the browser
     * 
     * @param url - an URL-String which represents the Web-Site to open in the
     *            browser. example: url = "http://www.google.com"
     * @throws FixtureException Exception occurs when browser can not open the given URL. 
     */
    @FixtureMethod
    public void goToUrl(String url) throws FixtureException {
        driver.get(url);
    }

    /**
     * close the Browser window
     */
    @FixtureMethod
    public void closeBrowser() throws FixtureException {
        // drive.close() leads to exception with Selenium V. 3.5.3 and Firefox
        // 55.0.3 preferred method -> just driver.quit();
        driver.quit();
        logger.trace("Web-Driver quitting completed.");
        driver = null;
        logger.trace("Web-Driver is set to null.");
    }

    /**
     * This method waits explicitly for a WebElement in the DOM-Object until the
     * given timeOut is reached before an Exception is thrown.
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @param timeOutInSeconds The max timeout in seconds when an element is
     *            expected before a NotFoundException will be thrown.
     * @throws FixtureException Exception occurs when wait until an element is 
     *         found on a Web-Element can not be performed. 
     */
    @FixtureMethod
    public void waitUntilElementFound(String elementLocator, LocatorStrategy locatorType, int timeOutInSeconds)
            throws FixtureException {
        logger.debug("Waiting max {} seconds until element {} with strategy {} is found", timeOutInSeconds,
                elementLocator, locatorType);
        WebDriverWait webDriverWait = new WebDriverWait(driver, timeOutInSeconds);
        logger.trace("Timeout for WebDriverWait is set to {}.", timeOutInSeconds);
        try {
            webDriverWait.until(ExpectedConditions.presenceOfElementLocated(getBy(elementLocator, locatorType)));
            logger.trace("WebDriverWait executed until element with elementlocator {} found.", elementLocator);
        } catch (TimeoutException e) {
            throw new FixtureException("timeout during wait for element", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "timeout", Long.valueOf(timeOutInSeconds)),
                    e);
        }
    }

    /**
     * First empties the input field and then types given text into input fields on a specified Gui-Widget
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @param value String which is set into the textfield
     * @throws FixtureException Exception occurs when sendKeys can not be performed on a Web-Element. 
     */
    @FixtureMethod
    public void typeInto(String elementLocator, LocatorStrategy locatorType, String value) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        try {
            element.clear();
            element.sendKeys(value);
        } catch (IllegalArgumentException e) {
            throw new FixtureException("string to be typed into element cannot be null", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "value", value.toString()), //
                    e);
        }
    }

    /**
     * First empties the input field and then types given text obfuscated into input fields on a specified Gui-Widget.
     * E.g. a HTML input field with type "password".
     * The specialty about the text which is typed in is, that it will be obfuscated in log files
     * 
     * @param elementLocator Locator for Gui-Widget.
     * @param locatorType Type of locator for Gui-Widget.
     * @param value A masked String which is set into the textfield where the type is password.
     * @throws FixtureException Exception occurs when a secret text will be typed into a 
     *         web input field which HTML-Atrribute is not type="password". 
     */
    @FixtureMethod
    public void typeConfidentialIntoConfidential(String elementLocator, LocatorStrategy locatorType, //
            MaskingString value) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        if (element.getAttribute("type").equals("password")) {
            try {
                element.clear();
                element.sendKeys(value.get());
            } catch (IllegalArgumentException e) {
                throw new FixtureException("string to be typed into element cannot be null", //
                        FixtureException.keyValues("elementLocator", elementLocator, //
                                "locatorType", locatorType.toString()),
                        e);
            }

        } else {
            throw new FixtureException(
                    "The searched Web-Element is not of type [password] further processing aborted."
                            + "Please prefer an input field of type password ", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString()));
        }
    }

    /**
     * First empties the input field and then types given text not obfuscated into a non secure input field
     * on a specified Gui-Widget. The specialty about the text which is typed in is, 
     * that it will be not obfuscated in log files
     * 
     * @param elementLocator Locator for Gui-Widget as {@link String}.
     * @param locatorType Type of locator for Gui-Widget as enum {@link LocatorStrategy}.
     * @param value A masked {@link String} which is set into the textfield.
     * @throws FixtureException Exception occurs when sendKeys can not be performed on a Web-Element. 
     */
    @FixtureMethod
    public void typeConfidentialInto(String elementLocator, LocatorStrategy locatorType, //
            MaskingString value) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        try {
            element.clear();
            element.sendKeys(value.get());
        } catch (IllegalArgumentException e) {
            throw new FixtureException("string to be typed into element cannot be null", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString()),
                    e);
        }
    }

    /**
     * Clears the given input field.
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @throws FixtureException  Exception occurs when input field could not be emptied. 
     */
    @FixtureMethod
    public void clear(String elementLocator, LocatorStrategy locatorType) throws FixtureException {
        try {
            WebElement element = getWebElement(elementLocator, locatorType);
            element.clear();
        } catch (Exception e) {
            throw new FixtureException("Could not find Web Element: " + elementLocator, //
                    FixtureException.keyValues("elementLocator", elementLocator, "locatorType",
                            locatorType.toString()),
                    e);
        }
    }

    /**
     * click on a specified Gui-Widget
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     */
    @FixtureMethod
    public void clickOn(String elementLocator, LocatorStrategy locatorType) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        try {
            element.click();
        } catch (StaleElementReferenceException e) {
            logPageSource(getPageSource());
            throw new FixtureException("element to click seems to no longer exist", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "element", element.toString(), //
                            "pageSource", getPageSource()),
                    e);
        }
    }

    /**
     * @return The title of the actual accessed html web site.
     */
    @FixtureMethod
    public String getTitle() throws FixtureException {
        return driver.getTitle();
    }

    /**
     * Submits WebElements like forms ore whole websites
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     */
    @FixtureMethod
    public void submit(String elementLocator, LocatorStrategy locatorType) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        try {
            element.submit();
        } catch (NoSuchElementException e) {
            logPageSource(getPageSource());
            throw new FixtureException("element seems not to be part of a form and cannot be submitted", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "element", element.toString(), //
                            "pageSource", getPageSource()),
                    e);
        }
    }

    /**
     * Returns the innerText content of an HTML-Element e.g. {@code <button>My Button</button>}
     * returns the text "My Button". If the value will not be found, a FixtureException will be thrown.
     * The expectation is: the value has to be available on the HTML site, because the user typed this value 
     * before this action.          
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @return innerText of a HTML-Element
     * @throws FixtureException
     */
    @FixtureMethod
    public String readValue(String elementLocator, LocatorStrategy locatorType) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        String value = getValueOfWebElement(element);
        if (value == null) {
            throw new FixtureException("No value found for given locator " + elementLocator, //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "element", element.toString(), //
                            "value", value));
        }
        return value;
    }

    protected String getValueOfWebElement(WebElement element) {
        String value = null;
        String readValueThroughText = element.getText();
        // Selenium getText() vs. getAttribute("value") see https://sqa.stackexchange.com/questions/24463/selenium-webdriver-gettext-vs-getattribute
        if ((readValueThroughText != null) && (!readValueThroughText.isEmpty())) {
            value = readValueThroughText;
            logger.trace("readValueThroughText -> element.getText() returned elements successfully");
        } else {
            String readValueThroughAttribute = element.getAttribute("value");
            if ((readValueThroughAttribute != null) && (!readValueThroughAttribute.isEmpty())) {
                value = readValueThroughAttribute;
                logger.trace("readValueThroughAttribute -> element.getAttribute(\"value\") returned "
                        + "elements successfully {}");
            } else {
                logger.trace("readValueThroughAttribute -> element.getAttribute(\"value\") and element.getText() "
                        + "returned no result for element {}", element.getTagName());
                value = null;
            }
        }
        logger.trace("getValueOfWebElement returned: \n{} for element {}", value, element.getTagName());
        return value;
    }

    /**
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @param value to be selected in a Selectionbox
     * @throws InterruptedException
     */
    @FixtureMethod
    public void selectElementInSelection(String elementLocator, LocatorStrategy locatorType, String value)
            throws FixtureException {
        clickOn(elementLocator, locatorType);
        logger.debug("Select element {} in selection {} located by: {}.", value, elementLocator, locatorType);
        wrappedSleep(300, "sleep after click on element was interrupted", FixtureException.keyValues(
                "elementLocator", elementLocator, "locatorType", locatorType.toString(), "value", value));
        logger.trace("wait for 300 ms");
        WebElement element = getWebElement(elementLocator, locatorType);
        try {
            new Select(element).selectByVisibleText(value);
            logger.trace("Selected value: {}", value);
        } catch (NoSuchElementException e) {
            throw new FixtureException(
                    "element could not be selected by visible text (option not part of this selection)", //
                    FixtureException.keyValues("elementLoactor", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "value", value, "element", element.toString()),
                    e);
        } catch (UnexpectedTagNameException e) {
            throw new FixtureException("element expected to be tag SELECT", //
                    FixtureException.keyValues("elementLoactor", elementLocator, //
                            "locatorType", locatorType.toString(), //
                            "value", value, //
                            "element", element.toString()),
                    e);
        }
    }

    /**
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @return a JsonObject of the values in a Selectionbox
     * @throws InterruptedException
     */
    @FixtureMethod
    public JsonObject getOptionsInSelection(String elementLocator, LocatorStrategy locatorType)
            throws FixtureException {
        JsonObject availableOptions = new JsonObject();
        clickOn(elementLocator, locatorType);
        wrappedSleep(300, "sleep after get options in selection was interrupted", FixtureException
                .keyValues("elementLocator", elementLocator, "locatorType", locatorType.toString()));
        Select selection = new Select(getWebElement(elementLocator, locatorType));
        for (WebElement webElement : selection.getAllSelectedOptions()) {
            availableOptions.addProperty(webElement.getText(), webElement.getText());
        }
        logger.trace("All options for the selection with locator = {} and locatorType = {} :  " + "{}",
                elementLocator, locatorType, availableOptions.toString());
        return availableOptions;
    }

    /**
     * 
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @return true if a checkable Gui-Widget is checked, false otherwise.
     */
    @FixtureMethod
    public Boolean checkEnabled(String elementLocator, LocatorStrategy locatorType) throws FixtureException {
        WebElement element = getWebElement(elementLocator, locatorType);
        boolean enabled = element.isEnabled();
        logger.debug("Element with locator: {} and locatorType: {} returned for " + "checkEnabled = {}",
                elementLocator, locatorType, enabled);
        return enabled;
    }

    /**
     * @param elementLocator Locator for Gui-Widget
     * @param locatorType Type of locator for Gui-Widget
     * @return {@code WebElement} where the Locator String begins in a specific
     *         manner.
     */
    protected WebElement getWebElement(String elementLocator, LocatorStrategy locatorType) throws FixtureException {
        if (executeScript != null) {
            executeScript();
        }

        logger.debug("Lookup element with locator \"{}\" and locatorType \"{}\"", elementLocator,
                locatorType.name());
        WebElement result = null;
        try {
            result = driver.findElement(getBy(elementLocator, locatorType));
            logger.trace("Lookup element was successful.");
        } catch (NoSuchElementException exception) {
            logPageSource(getPageSource());
            throw new FixtureException("element could not be located on the current page", //
                    FixtureException.keyValues("elementLocator", elementLocator, //
                            "locatorType", locatorType.toString(), "pageSource", getPageSource(), "url",
                            driver.getCurrentUrl(), "title", driver.getTitle()));
        }
        return result;
    }

    private By getBy(String elementLocator, LocatorStrategy locatorType) {
        By result = null;
        switch (locatorType) {
        case XPATH:
            result = By.xpath(elementLocator);
            logger.trace("Element {} searched by XPATH", elementLocator);
            break;
        case LINK:
            result = By.linkText(elementLocator);
            logger.trace("Element {} searched by LINKTEXT", elementLocator);
            break;
        case ID:
            result = By.id(elementLocator);
            logger.trace("Element {} searched by ID", elementLocator);
            break;
        case CSS:
            result = By.cssSelector(elementLocator);
            logger.trace("Element {} searched by CSS", elementLocator);
            break;
        default:
            result = By.name(elementLocator);
            logger.trace("Element {} searched by NAME", elementLocator);
            break;
        }
        return result;
    }

    /**
     * executes a given Javascript file. Name or respectively path of script must be
     * set before using method {@code setExecuteScript()}.
     */
    private void executeScript() throws FixtureException {
        try {
            logger.trace("execute script begin {}", executeScript);
            BufferedReader br = new BufferedReader(new FileReader(executeScript));
            StringBuilder sb = new StringBuilder();
            while (br.ready()) {
                sb.append(br.readLine()).append("\n");
            }
            br.close();
            logger.trace("execute script end {}", executeScript);
            logger.trace("Script for execution : {}", sb.toString());
            ((JavascriptExecutor) driver).executeScript(sb.toString());
        } catch (IOException e) {
            logger.error("Can't read java script", e);
            throw new FixtureException("could not execute javascript", //
                    FixtureException.keyValues("executeScript", executeScript), e);
        }
    }

    protected void wrappedSleep(long ms, String msg, Map<String, Object> keyValueMap) throws FixtureException {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            keyValueMap.put("timeout", Long.valueOf(ms));
            throw new FixtureException(msg, keyValueMap, e);
        }
    }

    /**
     * A special keyboard key is pressed.
     * 
     * @param specialKey
     *            the key to press (@see org.openqa.selenium.Keys)
     * @throws FixtureException
     *             if key is invalid
     */
    @FixtureMethod
    public void pressSpecialKey(String specialKey) throws FixtureException {
        logger.debug("Press special Key : {}.", specialKey);
        if (specialKey == null || specialKey.trim().isEmpty()) {
            throw new FixtureException("Invalid or empty key!");
        }

        try {
            Keys seleniumKey = Keys.valueOf(specialKey.trim().toUpperCase());
            new Actions(driver).sendKeys(seleniumKey).build().perform();
            logger.trace("Press special key performed with key : {}", specialKey);
        } catch (IllegalArgumentException e) {

            throw new FixtureException("Key cannot be converted",
                    FixtureException.keyValues("key", specialKey, "allowed-keys", join(Keys.values())), e);
        }
    }

    private String join(Keys[] keys) {
        ArrayList<Keys> allKeyentries = new ArrayList<>(Arrays.asList(keys));
        String allKeyEntries = allKeyentries.stream().map(n -> n.name()).collect(Collectors.joining("\", \""));
        logger.trace("All special key entries: {}", allKeyentries);
        return allKeyEntries;
    }

    /**
     * Checks if a specified text appears on a web site. Even just typed text artifacts 
     * which are currently inserted into the DOM. 
     * The expectation is: The user just wants to search for a specified text on a web site 
     * without an error being reported if the text is not found on the web site. With this
     * knowledge the user can decide what to do in the test. Using an assertion or just   
     * assigning the result of this method to a variable for later usage.
     * 
     * @param textTobeFound
     * @return true if searched text is found on a HTML Page and in DOM , false otherwise
     * @throws FixtureException
     */
    @FixtureMethod
    public boolean isTextOnPage(String textTobeFound) throws FixtureException {
        // First looking in the page source for fix Web Elements
        logger.debug("Is text '{}' on page?", textTobeFound);
        if (checkForConstantWebElements(textTobeFound)) {
            return true;
        }
        // Now check for dynamic Web Elements in DOM 
        return checkForDynamicWebElements(textTobeFound, getAllWebElementsOnPage());
    }

    protected List<WebElement> getAllWebElementsOnPage() {
        List<WebElement> elements = driver.findElements(By.xpath("//*"));
        return elements;
    }

    protected boolean checkForConstantWebElements(String textTobeFound) {
        String pageSource = getPageSource();
        logPageSource(pageSource);
        boolean textFoundOnPageSource = pageSource.contains(textTobeFound);
        logger.trace("Text \"{}\" is found on page source : {}", textTobeFound, textFoundOnPageSource);
        return textFoundOnPageSource;
    }

    protected String getPageSource() {
        String pageSource = driver.getPageSource();
        return pageSource;
    }

    protected void logPageSource(String pageSource) {
        logger.trace("PageSource: \n "
                + "************************ Begin Pagesource ********************************************************"
                + "\n {} \n "
                + "************************* End Pagesource *********************************************************",
                pageSource);
    }

    protected boolean checkForDynamicWebElements(String textTobeFound, List<WebElement> elements) {
        boolean textOnpage = false;
        logger.trace("Text \"{}\" is not found on static HTML, now we try all values of existing Web-Elements",
                textTobeFound);
        for (WebElement element : elements) {
            String valueOfWebElement = getValueOfWebElement(element);
            if (valueOfWebElement != null && valueOfWebElement.contains(textTobeFound)) {
                textOnpage = true;
                logger.trace("Text : \"{}\" is found on page", textTobeFound);
                break;
            }
        }
        return textOnpage;
    }

}