public static String quote(String text) {
    return "\"" + text + "\"";
} public static Capabilities addCapabilities(Capabilities capabilities, Map<String, ?> capabilitiesToAdd) {
    if (capabilities == null && capabilitiesToAdd == null) {
        return new DesiredCapabilities();
    }

    if (capabilities == null) {
        return new DesiredCapabilities(capabilitiesToAdd);
    }

    if (capabilitiesToAdd == null) {
        return capabilities;
    }

    return new DesiredCapabilities(capabilities, new DesiredCapabilities(capabilitiesToAdd));
} public static String convertToJsonString(Capabilities capabilities) {
    if (capabilities == null) {
        return "{}";
    }
    Gson gson = new GsonBuilder().setPrettyPrinting().create();
    return gson.toJson(capabilities.asMap());
} public static String getCurrentDateAndTime() {
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH.mm");
    return df.format(new Date());
} public static TimeUnit getUnitFromImplicitlyWaitAnnotation(TestClass testClass, FrameworkMethod testMethod) {
    if (hasImplicitlyWaitTestMethodAnnotation(testMethod)) {
        return testMethod.getAnnotation(ImplicitlyWait.class).unit();
    } else {
        return testClass.getJavaClass().getAnnotation(ImplicitlyWait.class).unit();
    }
} public static long getValueFromImplicitlyWaitAnnotation(TestClass testClass, FrameworkMethod testMethod) {
    if (hasImplicitlyWaitTestMethodAnnotation(testMethod)) {
        return testMethod.getAnnotation(ImplicitlyWait.class).value();
    } else {
        return testClass.getJavaClass().getAnnotation(ImplicitlyWait.class).value();
    }
} public static boolean hasImplicitlyWaitAnnotation(TestClass testClass, FrameworkMethod testMethod) {
    return hasImplicitlyWaitTestClassAnnotation(testClass) || hasImplicitlyWaitTestMethodAnnotation(testMethod);
} public static boolean hasScreenshotPathAnnotation(TestClass testClass) {
    return testClass.getJavaClass().getAnnotation(ScreenshotsPath.class) != null;
} public static boolean hasTakeScreenshotOnFailureAnnotation(TestClass testClass) {
    return testClass.getJavaClass().getAnnotation(TakeScreenshotOnFailure.class) != null;
} public static Capabilities removeCapabilities(Capabilities capabilities, String... keysToRemove) {
    if (capabilities == null) {
        return new DesiredCapabilities();
    }

    final Set<String> keysToRemoveSet = Sets.newHashSet(keysToRemove);
    capabilities = new DesiredCapabilities(Maps.filterKeys(capabilities.asMap(), new Predicate<String>() {
        @Override
        public boolean apply(String key) {
            return !keysToRemoveSet.contains(key);
        }
    }));

    return capabilities;
} public class WebDriverExtensionFieldDecorator extends DefaultFieldDecorator {

    private final WebDriver driver;
    private final ObjectPool pool;
    private final WebComponentFactory webComponentFactory;
    private final WebComponentListFactory webComponentListFactory;
    private ParameterizedType genericTypeArguments;

    public void setGenericTypeArguments(ParameterizedType genericTypeArguments) {
        this.genericTypeArguments = genericTypeArguments;
    }

    public WebDriverExtensionFieldDecorator(final WebDriver driver) {
        super(new WebDriverExtensionElementLocatorFactory(driver, driver));
        this.driver = driver;
        this.pool = new ObjectPool(driver);
        this.webComponentFactory = new DefaultWebComponentFactory();
        this.webComponentListFactory = new DefaultWebComponentListFactory(webComponentFactory);
        this.genericTypeArguments = null;
    }

    public WebDriverExtensionFieldDecorator(final SearchContext searchContext, final WebDriver driver) {
        super(new WebDriverExtensionElementLocatorFactory(searchContext, driver));
        this.driver = driver;
        this.pool = new ObjectPool(driver);
        this.webComponentFactory = new DefaultWebComponentFactory();
        this.webComponentListFactory = new DefaultWebComponentListFactory(webComponentFactory);
        this.genericTypeArguments = null;
    }

    public WebDriverExtensionFieldDecorator(final SearchContext searchContext, final WebDriver driver, final ParameterizedType genericTypeArguments) {
        super(new WebDriverExtensionElementLocatorFactory(searchContext, driver));
        this.driver = driver;
        this.pool = new ObjectPool(driver);
        this.webComponentFactory = new DefaultWebComponentFactory();
        this.webComponentListFactory = new DefaultWebComponentListFactory(webComponentFactory);
        this.genericTypeArguments = genericTypeArguments;
    }

    @Override
    public Object decorate(ClassLoader loader, Field field) {
        try {
            if (isDecoratableWebComponent(field)) {
                if (field.getGenericType() instanceof TypeVariable) {
                    return decorateWebComponent(loader, field, genericTypeArguments);
                } else if (field.getGenericType() instanceof ParameterizedType) {
                    return decorateWebComponent(loader, field, (ParameterizedType) field.getGenericType());
                } else {
                    return decorateWebComponent(loader, field, null);
                }
            }
            if (isDecoratableWebComponentList(field)) {
                Type listType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
                if (listType instanceof TypeVariable) {
                    return decorateWebComponentList(loader, field, genericTypeArguments);
                } else if (listType instanceof ParameterizedType) {
                    return decorateWebComponentList(loader, field, (ParameterizedType) listType);
                } else {
                    return decorateWebComponentList(loader, field, null);
                }
            }
            if (isDecoratableSiteObject(field)) {
                return pool.getSiteObject(field, this);
            }
            if (isDecoratablePageObject(field)) {
                return pool.getPageObject(field, this);
            }
            if (isDecoratableRepositoryObject(field)) {
                return pool.getRepositoryObject(field, this);
            }
            if ("wrappedWebElement".equals(field.getName())) {
                return null;
            }
            if ("delegateWebElement".equals(field.getName())) {
                return null;
            }
            return super.decorate(loader, field);
        } catch (Exception ex) {
            if (ex instanceof WebDriverExtensionException) {
                throw (WebDriverExtensionException) ex; // re-throw it
            } else {
                throw new WebDriverExtensionException("Failed to decorate field " + field.getName() + " in class " + field.getDeclaringClass(), ex);
            }
        }

    }

    private boolean isDecoratableWebComponent(Field field) {
        if (!WebComponent.class.isAssignableFrom(field.getType())) {
            return false;
        }

        return true;
    }

    private boolean isDecoratableWebComponentList(Field field) {
        if (!List.class.isAssignableFrom(field.getType())) {
            return false;
        }

        // Type erasure in Java isn't complete. Attempt to discover the generic
        // type of the list.
        Type genericType = field.getGenericType();
        if (!(genericType instanceof ParameterizedType)) {
            return false;
        }

        Type listType = ((ParameterizedType) genericType).getActualTypeArguments()[0];

        if (listType instanceof TypeVariable) {
            if (!WebComponent.class.isAssignableFrom(getListType(field, genericTypeArguments))) {
                return false;
            }
        } else if (listType instanceof ParameterizedType) {
            if (!WebComponent.class.isAssignableFrom((Class) ((ParameterizedType) listType).getRawType())) {
                return false;
            }
        } else {
            if (!WebComponent.class.isAssignableFrom((Class) listType)) {
                return false;
            }
        }

        if (field.getAnnotation(FindBy.class) == null
                && field.getAnnotation(FindBys.class) == null
                && field.getAnnotation(FindAll.class) == null) {
            return false;
        }

        return true;
    }

    private boolean isDecoratablePageObject(Field field) {
        if (!WebPage.class.isAssignableFrom(field.getType())) {
            return false;
        }

        return true;
    }

    private boolean isDecoratableSiteObject(Field field) {
        if (!WebSite.class.isAssignableFrom(field.getType())) {
            return false;
        }

        return true;
    }

    private boolean isDecoratableRepositoryObject(Field field) {
        if (!WebRepository.class.isAssignableFrom(field.getType())) {
            return false;
        }

        return true;
    }

    private Object decorateWebComponent(ClassLoader loader, Field field, ParameterizedType genericTypeArguments) {
        ElementLocator locator = factory.createLocator(field);
        Class type = ReflectionUtils.getType(field, genericTypeArguments);
        final WebElement webElement = proxyForLocator(loader, locator);
        final WebComponent webComponent = webComponentFactory.create(type, webElement);
        PageFactory.initElements(new WebDriverExtensionFieldDecorator(webElement, driver, genericTypeArguments), webComponent);
        webComponent.delegateWebElement = WebDriverExtensionAnnotations.getDelagate(webComponent);
        return webComponent;
    }

    private Object decorateWebComponentList(final ClassLoader loader, final Field field, ParameterizedType genericTypeArguments) {
        ElementLocator locator = factory.createLocator(field);
        Class listType = ReflectionUtils.getListType(field, genericTypeArguments);
        List<WebElement> webElements = proxyForListLocator(loader, locator);
        return webComponentListFactory.create(listType, webElements, driver, genericTypeArguments);
    }
} public class WebDriverExtensionsContext {

    private static InheritableThreadLocal<WebDriver> threadLocalDriver = new InheritableThreadLocal<>();

    public static WebDriver getDriver() {
        if (threadLocalDriver.get() == null) {
            throw new WebDriverExtensionException("Driver in WebDriverExtensionsContext is not set. Please set the driver with WebDriverExtensionsContext.setDriver(...) method before using the WebDriverExtensions framework. Note that the driver will be thread safe since ThreadLocal is used so don't worry about thread safety.");
        }
        return threadLocalDriver.get();
    }

    public static void removeDriver() {
        threadLocalDriver.remove();
    }

    public static void setDriver(WebDriver driver) {
        threadLocalDriver.set(driver);
    }

} public class WebDriverProperties {
    public static final String CHROME_DRIVER_PROPERTY_NAME = "webdriver.chrome.driver";
    public static final String CHROME_BINARY_PROPERTY_NAME = "chrome.binary.path";
    public static final String FIREFOX_DRIVER_PROPERTY_NAME = "webdriver.gecko.driver";
    public static final String IE_DRIVER_PROPERTY_NAME = "webdriver.ie.driver";
    public static final String PHANTOMJS_BINARY_PROPERTY_NAME = "phantomjs.binary.path";
    public static final String OPERA_DRIVER_PROPERTY_NAME = "webdriver.opera.driver";
    public static final String EDGE_DRIVER_PROPERTY_NAME = "webdriver.edge.driver";
    public static final String DISABLED_BROWSERS_PROPERTY_NAME = "webdriverextensions.disabledbrowsers";
} public abstract class AnnotationUtils {

    /** The attribute name for annotations with a single element */
    static final String VALUE = "value";

    private static final Map<Class, Boolean> annotatedInterfaceCache = new WeakHashMap<>();


    private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
        synchronized (annotatedInterfaceCache) {
            Boolean flag = annotatedInterfaceCache.get(iface);
            if (flag != null) {
                return flag;
            }
            boolean found = false;
            for (Method ifcMethod : iface.getMethods()) {
                if (ifcMethod.getAnnotations().length > 0) {
                    found = true;
                    break;
                }
            }
            annotatedInterfaceCache.put(iface, found);
            return found;
        }
    }


    /**
     * Retrieve the given annotation's attributes as a Map, preserving all attribute types as-is.
     * @param annotation the annotation to retrieve the attributes for
     * @return the Map of annotation attributes, with attribute names as keys and
     * corresponding attribute values as values
     */
    public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
        return getAnnotationAttributes(annotation, false);
    }

    /**
     * Retrieve the given annotation's attributes as a Map.
     * @param annotation the annotation to retrieve the attributes for
     * @param classValuesAsString whether to turn Class references into Strings (for compatibility with
     * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as Class references
     * @return the Map of annotation attributes, with attribute names as keys and
     * corresponding attribute values as values
     */
    public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) {
        Map<String, Object> attrs = new HashMap<>();
        Method[] methods = annotation.annotationType().getDeclaredMethods();
        for (Method method : methods) {
            if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
                try {
                    Object value = method.invoke(annotation);
                    if (classValuesAsString) {
                        if (value instanceof Class) {
                            value = ((Class) value).getName();
                        }
                        else if (value instanceof Class[]) {
                            Class[] clazzArray = (Class[]) value;
                            String[] newValue = new String[clazzArray.length];
                            for (int i = 0; i < clazzArray.length; i++) {
                                newValue[i] = clazzArray[i].getName();
                            }
                            value = newValue;
                        }
                    }
                    attrs.put(method.getName(), value);
                }
                catch (Exception ex) {
                    throw new IllegalStateException("Could not obtain annotation attribute values", ex);
                }
            }
        }
        return attrs;
    }

    /**
     * Retrieve the <em>value</em> of the <code>&quot;value&quot;</code> attribute of a
     * single-element Annotation, given an annotation instance.
     * @param annotation the annotation instance from which to retrieve the value
     * @return the attribute value, or <code>null</code> if not found
     * @see #getValue(Annotation, String)
     */
    public static Object getValue(Annotation annotation) {
        return getValue(annotation, VALUE);
    }

    /**
     * Retrieve the <em>value</em> of a named Annotation attribute, given an annotation instance.
     * @param annotation the annotation instance from which to retrieve the value
     * @param attributeName the name of the attribute value to retrieve
     * @return the attribute value, or <code>null</code> if not found
     * @see #getValue(Annotation)
     */
    public static Object getValue(Annotation annotation, String attributeName) {
        try {
            Method method = annotation.annotationType().getDeclaredMethod(attributeName, new Class[0]);
            return method.invoke(annotation);
        }
        catch (Exception ex) {
            return null;
        }
    }

    /**
     * Retrieve the <em>default value</em> of the <code>&quot;value&quot;</code> attribute
     * of a single-element Annotation, given an annotation instance.
     * @param annotation the annotation instance from which to retrieve the default value
     * @return the default value, or <code>null</code> if not found
     * @see #getDefaultValue(Annotation, String)
     */
    public static Object getDefaultValue(Annotation annotation) {
        return getDefaultValue(annotation, VALUE);
    }

    /**
     * Retrieve the <em>default value</em> of a named Annotation attribute, given an annotation instance.
     * @param annotation the annotation instance from which to retrieve the default value
     * @param attributeName the name of the attribute value to retrieve
     * @return the default value of the named attribute, or <code>null</code> if not found
     * @see #getDefaultValue(Class, String)
     */
    public static Object getDefaultValue(Annotation annotation, String attributeName) {
        return getDefaultValue(annotation.annotationType(), attributeName);
    }

    /**
     * Retrieve the <em>default value</em> of the <code>&quot;value&quot;</code> attribute
     * of a single-element Annotation, given the {@link Class annotation type}.
     * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
     * @return the default value, or <code>null</code> if not found
     * @see #getDefaultValue(Class, String)
     */
    public static Object getDefaultValue(Class<? extends Annotation> annotationType) {
        return getDefaultValue(annotationType, VALUE);
    }

    /**
     * Retrieve the <em>default value</em> of a named Annotation attribute, given the {@link Class annotation type}.
     * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
     * @param attributeName the name of the attribute value to retrieve.
     * @return the default value of the named attribute, or <code>null</code> if not found
     * @see #getDefaultValue(Annotation, String)
     */
    public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
        try {
            Method method = annotationType.getDeclaredMethod(attributeName, new Class[0]);
            return method.getDefaultValue();
        }
        catch (Exception ex) {
            return null;
        }
    }

} public class DriverPathLoader {

    private DriverPathLoader() {}

    public static void loadDriverPaths(DriverPaths driverPaths) {
        loadChromeDriverPath(driverPaths != null ? driverPaths.chrome() : null);
        loadFirefoxDriverPath(driverPaths != null ? driverPaths.firefox() : null);
        loadEdgeDriverPath(driverPaths != null ? driverPaths.edge() : null);
        loadInternetExplorerDriverPath(driverPaths != null ? driverPaths.internetExplorer() : null);
        loadPhantomJsDriverPath(driverPaths != null ? driverPaths.phantomJs() : null);
        makeSureDriversAreExecutable();
    }

    private static void loadChromeDriverPath(String path) {
        PropertyUtils.setPropertyIfNotExists(CHROME_DRIVER_PROPERTY_NAME, path);
        PropertyUtils.setPropertyIfNotExists(CHROME_DRIVER_PROPERTY_NAME, getChromeDriverDefaultPath());
    }

    private static void loadFirefoxDriverPath(String path) {
        PropertyUtils.setPropertyIfNotExists(FIREFOX_DRIVER_PROPERTY_NAME, path);
        PropertyUtils.setPropertyIfNotExists(FIREFOX_DRIVER_PROPERTY_NAME, getFirefoxDriverDefaultPath());
    }

    private static void loadEdgeDriverPath(String path) {
        PropertyUtils.setPropertyIfNotExists(EDGE_DRIVER_PROPERTY_NAME, path);
        PropertyUtils.setPropertyIfNotExists(EDGE_DRIVER_PROPERTY_NAME, getEdgeDefaultPath());
    }

    private static void loadInternetExplorerDriverPath(String path) {
        PropertyUtils.setPropertyIfNotExists(IE_DRIVER_PROPERTY_NAME, System.getProperty(INTERNET_EXPLORER_DRIVER_PROPERTY_NAME)); // Alternative property name that follows naming convention
        PropertyUtils.setPropertyIfNotExists(IE_DRIVER_PROPERTY_NAME, path);
        PropertyUtils.setPropertyIfNotExists(IE_DRIVER_PROPERTY_NAME, getInternetExplorerDriverDefaultPath());
    }

    private static void loadPhantomJsDriverPath(String path) {
        PropertyUtils.setPropertyIfNotExists(PHANTOMJS_BINARY_PROPERTY_NAME, path);
        PropertyUtils.setPropertyIfNotExists(PHANTOMJS_BINARY_PROPERTY_NAME, getPhantomJsDefaultPath());
    }

    private static void makeSureDriversAreExecutable() {
        FileUtils.makeExecutable(System.getProperty(CHROME_DRIVER_PROPERTY_NAME));
        FileUtils.makeExecutable(System.getProperty(IE_DRIVER_PROPERTY_NAME));
    }

    private static String getChromeDriverDefaultPath() {
        if (OsUtils.isWindows()) {
            return "drivers/chromedriver-windows-32bit.exe";
        } else if (OsUtils.isMac()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/chromedriver-mac-64bit")) || Files.notExists(Paths.get("./drivers/chromedriver-mac-32bit")))) {
                return "drivers/chromedriver-mac-64bit";
            } else {
                return "drivers/chromedriver-mac-32bit";
            }
        } else if (OsUtils.isLinux()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/chromedriver-linux-64bit")) || Files.notExists(Paths.get("./drivers/chromedriver-linux-32bit")))) {
                return "drivers/chromedriver-linux-64bit";
            } else {
                return "drivers/chromedriver-linux-32bit";
            }
        }
        return null;
    }

    private static String getFirefoxDriverDefaultPath() {
        if (OsUtils.isWindows()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/geckodriver-windows-64bit.exe")) || Files.notExists(Paths.get("./drivers/geckodriver-windows-32bit.exe")))) {
                return "drivers/geckodriver-windows-64bit.exe";
            } else {
                return "drivers/geckodriver-windows-32bit.exe";
            }
        } else if (OsUtils.isMac()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/geckodriver-mac-64bit")) || Files.notExists(Paths.get("./drivers/geckodriver-mac-32bit")))) {
                return "drivers/geckodriver-mac-64bit";
            } else {
                return "drivers/geckodriver-mac-32bit";
            }
        } else if (OsUtils.isLinux()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/geckodriver-linux-64bit")) || Files.notExists(Paths.get("./drivers/geckodriver-linux-32bit")))) {
                return "drivers/geckodriver-linux-64bit";
            } else {
                return "drivers/geckodriver-linux-32bit";
            }
        }
        return null;
    }

    private static String getEdgeDefaultPath() {
        if (OsUtils.isWindows()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/edgedriver-windows-64bit.exe")) || Files.notExists(Paths.get("./drivers/edgedriver-windows-32bit.exe")))) {
                return "drivers/edgedriver-windows-64bit.exe";
            } else {
                return "drivers/edgedriver-windows-32bit.exe";
            }
        }
        return null;
    }

    private static String getPhantomJsDefaultPath() {
        if (OsUtils.isWindows()) {
            return "drivers/phantomjs-windows-64bit.exe";
        } else if (OsUtils.isMac()) {
            return "drivers/phantomjs-mac-64bit";
        } else if (OsUtils.isLinux()) {
            if (OsUtils.is64Bit() && (Files.exists(Paths.get("./drivers/phantomjs-linux-64bit")) || Files.notExists(Paths.get("./drivers/phantomjs-linux-32bit")))) {
                return "drivers/phantomjs-linux-64bit";
            } else {
                return "drivers/phantomjs-linux-32bit";
            }
        }
        return null;
    }

    private static String getInternetExplorerDriverDefaultPath() {
        if (OsUtils.isWindows()) {
            if (!PropertyUtils.propertyExists(IE_DRIVER_USE64BIT_PROPERTY_NAME)
                    || !PropertyUtils.propertyExists(INTERNET_EXPLORER_DRIVER_USE64BIT_PROPERTY_NAME)) {
                if (OsUtils.isWindows10()) {
                    return "drivers/internetexplorerdriver-windows-64bit.exe";
                } else {
                    return "drivers/internetexplorerdriver-windows-32bit.exe";
                }
            } else {
                if (PropertyUtils.isTrue(IE_DRIVER_USE64BIT_PROPERTY_NAME)
                        || PropertyUtils.isTrue(INTERNET_EXPLORER_DRIVER_USE64BIT_PROPERTY_NAME)) {
                    return "drivers/internetexplorerdriver-windows-64bit.exe";
                } else {
                    return "drivers/internetexplorerdriver-windows-32bit.exe";
                }
            }
        }
        return null;
    }
} public class ScreenshotsPathLoader {

    private ScreenshotsPathLoader() {}
    
    public static void loadScreenshotsPath(ScreenshotsPath screenshotsPathAnnotation) {
        String screenshotsPathAnnotationValue = screenshotsPathAnnotation != null ? screenshotsPathAnnotation.value() : null;
        PropertyUtils.setPropertyIfNotExists(SCREENSHOTSPATH_PROPERTY_NAME, screenshotsPathAnnotationValue);
    }

} public class InstanceUtils {

    private InstanceUtils() {}
    
    public static <T> T newInstance(Class clazz, Class<T> returnedClazz) {
        try {
            return (T) clazz.getConstructor().newInstance();
        } catch (NoSuchMethodException ex) {
            throw new WebDriverExtensionException(ex);
        } catch (SecurityException ex) {
            throw new WebDriverExtensionException(ex);
        } catch (InstantiationException ex) {
            throw new WebDriverExtensionException(ex);
        } catch (IllegalAccessException ex) {
            throw new WebDriverExtensionException(ex);
        } catch (IllegalArgumentException ex) {
            throw new WebDriverExtensionException(ex);
        } catch (InvocationTargetException ex) {
            throw new WebDriverExtensionException(ex);
        }
    }

} public class OsUtils {

    private OsUtils() {}

    public static boolean isWindows() {
        return Platform.getCurrent().is(Platform.WINDOWS);
    }

    public static boolean isWindows10() {
        return Platform.getCurrent().is(Platform.WIN10);
    }

    public static boolean isMac() {
        return Platform.getCurrent().is(Platform.MAC);
    }

    public static boolean isLinux() {
        return Platform.getCurrent().is(Platform.LINUX);
    }

    public static boolean isCurrentPlatform(String platform) {
        try {
            return Platform.getCurrent().is(Platform.valueOf(platform));
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    public static boolean is64Bit() {
        return com.sun.jna.Platform.is64Bit();
    }
} public class PropertyUtils {

    private PropertyUtils() {}
    
    public static boolean isTrue(String key) {
        return BooleanUtils.toBoolean(System.getProperty(key));
    }

    public static boolean propertyExists(String key) {
        return System.getProperty(key) != null;
    }

    public static void setPropertyIfNotExists(String key, String value) {
        if (value == null || StringUtils.isBlank(value)) {
            return;
        }
        if (!propertyExists(key)) {
            System.setProperty(key, value);
        }
    }
}
import static com.github.webdriverextensions.internal.utils.StringUtils.quote;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.addCapabilities;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.convertToJsonString;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.getCurrentDateAndTime;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.getUnitFromImplicitlyWaitAnnotation;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.getValueFromImplicitlyWaitAnnotation;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.hasImplicitlyWaitAnnotation;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.hasScreenshotPathAnnotation;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.hasTakeScreenshotOnFailureAnnotation;
import static com.github.webdriverextensions.internal.utils.WebDriverUtils.removeCapabilities;
import static org.openqa.selenium.remote.CapabilityType.BROWSER_NAME;
import static org.openqa.selenium.remote.CapabilityType.PLATFORM;
import static org.openqa.selenium.remote.CapabilityType.VERSION;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.remote.BrowserType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.support.PageFactory;
import com.github.webdriverextensions.WebDriverExtensionFieldDecorator;
import com.github.webdriverextensions.WebDriverExtensionsContext;
import com.github.webdriverextensions.WebDriverProperties;
import com.github.webdriverextensions.internal.WebDriverExtensionException;
import com.github.webdriverextensions.internal.junitrunner.AnnotationUtils;
import com.github.webdriverextensions.internal.junitrunner.DriverPathLoader;
import com.github.webdriverextensions.internal.junitrunner.ScreenshotsPathLoader;
import com.github.webdriverextensions.internal.junitrunner.TakeScreenshotOnFailureRunListener;
import com.github.webdriverextensions.internal.utils.InstanceUtils;
import com.github.webdriverextensions.internal.utils.OsUtils;
import com.github.webdriverextensions.internal.utils.PropertyUtils;
import com.github.webdriverextensions.junitrunner.annotations.Android;
import com.github.webdriverextensions.junitrunner.annotations.Browser;
import com.github.webdriverextensions.junitrunner.annotations.Chrome;
import com.github.webdriverextensions.junitrunner.annotations.DriverPaths;
import com.github.webdriverextensions.junitrunner.annotations.Edge;
import com.github.webdriverextensions.junitrunner.annotations.Firefox;
import com.github.webdriverextensions.junitrunner.annotations.HtmlUnit;
import com.github.webdriverextensions.junitrunner.annotations.IPad;
import com.github.webdriverextensions.junitrunner.annotations.IPhone;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreAndroid;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreBrowser;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreChrome;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreEdge;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreFirefox;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreHtmlUnit;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreIPad;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreIPhone;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreInternetExplorer;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreOpera;
import com.github.webdriverextensions.junitrunner.annotations.IgnorePhantomJS;
import com.github.webdriverextensions.junitrunner.annotations.IgnoreSafari;
import com.github.webdriverextensions.junitrunner.annotations.InternetExplorer;
import com.github.webdriverextensions.junitrunner.annotations.Opera;
import com.github.webdriverextensions.junitrunner.annotations.PhantomJS;
import com.github.webdriverextensions.junitrunner.annotations.RemoteAddress;
import com.github.webdriverextensions.junitrunner.annotations.Safari;
import com.github.webdriverextensions.junitrunner.annotations.ScreenshotsPath;
import com.google.gson.Gson;
        IgnoreIPad.class,
        IgnoreInternetExplorer.class,
        IgnoreOpera.class,
        IgnorePhantomJS.class,
        IgnoreSafari.class,
        IgnoreBrowser.class
    });

    private final Object childrenLock = new Object();
    private volatile Collection<FrameworkMethod> filteredTestAnnotatedMethods = null; // Guarded by childrenLock

    protected Collection<FrameworkMethod> getFilteredTestAnnotatedMethods() {
        if (filteredTestAnnotatedMethods == null) {
            synchronized (childrenLock) {
                if (filteredTestAnnotatedMethods == null) {
                    filteredTestAnnotatedMethods = Collections.unmodifiableCollection(createBrowserSpecificTestMethods(getTestAnnotatedMethods()));
                }
            }
        }
        return filteredTestAnnotatedMethods;
    }

    protected static List<String> getDisabledBrowsers() {
        String disabledBrowsersString = System.getProperty(WebDriverProperties.DISABLED_BROWSERS_PROPERTY_NAME, "");
        List<String> disabledBrowsersList = parseDisabledBrowserString(disabledBrowsersString);
	log.info("Browsers disabled by system property: {}", disabledBrowsersList);
        return disabledBrowsersList;
    }

    protected static List<String> parseDisabledBrowserString(String disabledBrowsersString) {
	List<String> result = new ArrayList<>();
        for (String disabledbrowserString : disabledBrowsersString.split(",")) {
            if (StringUtils.isNotBlank(disabledbrowserString)) {
                result.add(disabledbrowserString.trim());
            }
        }
	return result;
    }

    public WebDriverRunner(Class<?> klass) throws InitializationError {
        super(klass);
        DriverPathLoader.loadDriverPaths(getTestClass().getJavaClass().getAnnotation(DriverPaths.class));
        ScreenshotsPathLoader.loadScreenshotsPath(getTestClass().getJavaClass().getAnnotation(ScreenshotsPath.class));
    }

    /**
     * @deprecated retained for backward compatibility
     */
    @Deprecated
    @Override
    protected void validateInstanceMethods(List<Throwable> errors) {
        // Instead of calling super.validateInstanceMethods(errors)
        // make the same calls except skip adding the "No runnable methods" error
        validatePublicVoidNoArgMethods(After.class, false, errors);
        validatePublicVoidNoArgMethods(Before.class, false, errors);
        validateTestMethods(errors);
    }

    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        return new ArrayList<>(getFilteredTestAnnotatedMethods());
    }

    protected List<FrameworkMethod> createBrowserSpecificTestMethods(List<FrameworkMethod> testAnnotatedMethods) {
        List<FrameworkMethod> testMethods = new ArrayList<>();
        for (FrameworkMethod testAnnotatedMethod : testAnnotatedMethods) {
            TestMethodContext testMethodContext = new TestMethodContext().addBrowsersFromClassAnnotations(getTestClass()).addBrowsersFromMethodAnnotations(testAnnotatedMethod);
            if (!testMethodContext.getBrowsers().isEmpty()) {
                for (BrowserConfiguration browser : testMethodContext.getBrowsers()) {
                    testMethods.add(new WebDriverFrameworkMethod(browser, testAnnotatedMethod));
                }
            } else {
                // Not a WebDriverRunner Annotated test, treat as normal test
                testMethods.add(testAnnotatedMethod);
            }
        }
        return testMethods;
    }

    @Override
    public Description getDescription() {
        Description description = Description.createSuiteDescription(getName(),
                getRunnerAnnotations());
        for (FrameworkMethod child : getFilteredTestAnnotatedMethods()) {
            description.addChild(describeChild(child));
        }
        return description;
    }

    protected List<FrameworkMethod> getTestAnnotatedMethods() {
        List<FrameworkMethod> testAnnotatedMethods = getTestClass().getAnnotatedMethods(Test.class);
        List<FrameworkMethod> testMethods = new ArrayList<>();
        for (FrameworkMethod testAnnotatedMethod : testAnnotatedMethods) {
            testMethods.add(testAnnotatedMethod);
        }
        return testMethods;
    }

    @Override
    protected Object createTest() throws Exception {
        Object test = super.createTest();
        try {
            PageFactory.initElements(new WebDriverExtensionFieldDecorator(WebDriverExtensionsContext.getDriver()), test);
        } catch (WebDriverExtensionException e) {
            // Swallow "WebDriverExtensionsContext is not set" exceptions to allow running normal JUnit tests
        }
        return test;
    }

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        if (method instanceof WebDriverFrameworkMethod) {
            TestMethodContext testMethodContext = new TestMethodContext().addBrowsersFromClassAnnotations(getTestClass()).addBrowsersFromMethodAnnotations(method);
            BrowserConfiguration browser = ((WebDriverFrameworkMethod) method).getBrowser();
            Description description = describeChild(method);

            // Create test name
            String className = getTestClass().getJavaClass().getSimpleName();
            String methodName = method.getName();
            String testName = String.format("%s.%s", className, methodName);
            boolean hasRemoteAddress = PropertyUtils.propertyExists("webdriverextensions.remoteaddress")