package jp.vmi.selenium.webdriver; import java.util.Collections; import java.util.Set; import java.util.WeakHashMap; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.remote.UnreachableBrowserException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Manager of {@link WebDriver} instances. */ public class WebDriverManager implements WebDriverPreparator { private static final Logger log = LoggerFactory.getLogger(WebDriverManager.class); /** * Firefox. */ public static final String FIREFOX = FirefoxDriverFactory.BROWSER_NAME; /** * Chrome. */ public static final String CHROME = ChromeDriverFactory.BROWSER_NAME; /** * Internet Explorer. */ public static final String IE = IEDriverFactory.BROWSER_NAME; /** * Html Unit. */ public static final String HTMLUNIT = HtmlUnitDriverFactory.BROWSER_NAME; /** * Safari. */ public static final String SAFARI = SafariDriverFactory.BROWSER_NAME; /** * RemoteWebDriver */ public static final String REMOTE = RemoteWebDriverFactory.BROWSER_NAME; /** * AppiumWebDriver */ public static final String APPIUM = AppiumWebDriverFactory.BROWSER_NAME; /** * PhantomJS */ public static final String PHANTOMJS = PhantomJSDriverFactory.BROWSER_NAME; private static class Builder { private final WebDriverFactory factory; private final DriverOptions driverOptions; public Builder(WebDriverFactory factory, DriverOptions driverOptions) { this.factory = factory; this.driverOptions = new DriverOptions(driverOptions); } public boolean isParameterChanged(WebDriverFactory factory, DriverOptions driverOptions) { return !this.factory.equals(factory) || !this.driverOptions.toString().equals(driverOptions.toString()); } public WebDriver build() { return factory.newInstance(driverOptions); } } /** * System property name for user defined {@link WebDriverFactory}. */ public static final String WEBDRIVER_FACTORY = "jp.vmi.selenium.webdriver.factory"; private static final Set<WebDriverManager> managers = Collections.newSetFromMap(new WeakHashMap<WebDriverManager, Boolean>()); private WebDriverFactory factory; private DriverOptions driverOptions = new DriverOptions(); private Builder builder = null; private WebDriver driver = null; static { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { quitDriversOnAllManagers(); } }); } private static String getDriverName(WebDriver driver) { String name = driver.getClass().getSimpleName(); if (StringUtils.isNotBlank(name)) return name; else return driver.getClass().getName(); } /** * Construct WebDriverManager instance. * * @return WebDriverMangaer. */ public static WebDriverManager newInstance() { WebDriverManager manager = new WebDriverManager(); synchronized (managers) { managers.add(manager); } return manager; } /** * Constructor. */ private WebDriverManager() { setWebDriverFactory(System.getProperty(WEBDRIVER_FACTORY, FIREFOX)); } @Override public String getBrowserName() { return factory.getBrowserName(); } /** * Set current WebDriverFactory. * * @param factory WebDriverFactory instance. */ public void setWebDriverFactory(WebDriverFactory factory) { this.factory = factory; } /** * Set current WebDriverFactory name. * * @param factoryName WebDriverFactory name. */ public void setWebDriverFactory(String factoryName) { WebDriverFactory factory = lookupWebDriverFactory(factoryName); setWebDriverFactory(factory); } /** * Lookup WebDriverFactory by name. * * @param factoryName WebDriverFactory name. * * @return WebDriverFactory instance. */ public WebDriverFactory lookupWebDriverFactory(String factoryName) { if (StringUtils.isBlank(factoryName)) factoryName = FIREFOX; switch (factoryName.toLowerCase()) { case FIREFOX: return new FirefoxDriverFactory(); case CHROME: return new ChromeDriverFactory(); case IE: return new IEDriverFactory(); case SAFARI: return new SafariDriverFactory(); case HTMLUNIT: return new HtmlUnitDriverFactory(); case REMOTE: return new RemoteWebDriverFactory(); case APPIUM: return new AppiumWebDriverFactory(); case PHANTOMJS: return new PhantomJSDriverFactory(); default: try { return (WebDriverFactory) Class.forName(factoryName).newInstance(); } catch (Exception e) { throw new IllegalArgumentException("Invalid WebDriverFactory class name: " + factoryName, e); } } } /** * Get current driver options. * * @return DriverOptions instance. */ public DriverOptions getDriverOptions() { return driverOptions; } /** * Set current driver options. * * @param driverOptions DriverOptions instance. */ public void setDriverOptions(DriverOptions driverOptions) { this.driverOptions = driverOptions; } private boolean isBrowserUnreachable(Throwable t) { if (t instanceof UnreachableBrowserException) return true; String msg = t.getMessage(); if (msg != null && msg.startsWith("browser is undefined")) return true; return false; } private boolean isDriverReusable() { if (driver == null) return false; try { driver.getWindowHandle(); // ChromeDriver does not send UnreachableBrowserException. // TODO fix me when ChromeDriver is fixed. if (driver instanceof ChromeDriver) driver.getTitle(); return true; } catch (NoSuchWindowException e) { log.info("No focused window."); Set<String> handles = driver.getWindowHandles(); if (!handles.isEmpty()) { log.info("Activate a window."); driver.switchTo().window(handles.iterator().next()); return true; } log.warn("No window exists."); } catch (RuntimeException e) { if (!isBrowserUnreachable(e)) throw e; log.warn("Browser might crash."); } try { driver.quit(); } catch (Throwable e) { // no operation } return false; } @Override public synchronized WebDriver get() throws IllegalArgumentException { if (builder == null || builder.isParameterChanged(factory, driverOptions)) { builder = new Builder(factory, driverOptions); } else { if (isDriverReusable()) { log.info("Existing driver found."); return driver; } else { log.info("Restart driver."); } } quitDriver(); driver = builder.build(); log.info("Initialized: {}", getDriverName(driver)); return driver; } /** * Quit WebDriver instance. */ public synchronized void quitDriver() { if (driver == null) return; try { driver.quit(); } catch (Exception e) { log.warn(e.getMessage()); } finally { log.info("Quit: {}", getDriverName(driver)); } driver = null; } @Override protected void finalize() throws Throwable { quitDriver(); } /** * Quit WebDriver instances on all WebDriverManager instances. */ public static synchronized void quitDriversOnAllManagers() { synchronized (managers) { for (WebDriverManager manager : managers) manager.quitDriver(); } } }