View Javadoc

1   package com.atlassian.webdriver;
2   
3   import com.atlassian.browsers.BrowserConfig;
4   import com.atlassian.webdriver.browsers.AutoInstallConfiguration;
5   import com.atlassian.webdriver.utils.WebDriverUtil;
6   import com.google.common.base.Preconditions;
7   import com.google.common.base.Supplier;
8   import org.openqa.selenium.WebDriver;
9   import org.openqa.selenium.WebDriverException;
10  import org.openqa.selenium.ie.InternetExplorerDriver;
11  import org.openqa.selenium.remote.UnreachableBrowserException;
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  import java.lang.ref.WeakReference;
16  import java.util.Map;
17  import java.util.concurrent.ConcurrentHashMap;
18  
19  import static com.atlassian.webdriver.utils.WebDriverUtil.getUnderlyingDriver;
20  
21  /**
22   * <p/>
23   * Simple lifecycle aware Webdriver helper that will setup auto browsers and then retrieve a driver from the factory.
24   * Once the driver is running it will be re-used if the same browser property is retrieved again.
25   *
26   * <p/>
27   * When the runtime is shutdown it will handle cleaning up the browser. It also provides a way to manually quit all
28   * the registered drivers/browsers via {@link #shutdown()}. When that method is called, the shutdown hooks for those
29   * browser will be unregistered.
30   *
31   * @since 2.1
32   */
33  public class LifecycleAwareWebDriverGrid
34  {
35      private static final Logger log = LoggerFactory.getLogger(LifecycleAwareWebDriverGrid.class);
36      private final static Map<String,AtlassianWebDriver> drivers = new ConcurrentHashMap<String, AtlassianWebDriver>();
37      private volatile static AtlassianWebDriver currentDriver;
38  
39      private static final Map<String,WeakReference<Thread>> SHUTDOWN_HOOKS = new ConcurrentHashMap<String, WeakReference<Thread>>();
40  
41      private LifecycleAwareWebDriverGrid() {}
42  
43      /**
44       * Retrieves the driver from the {@link WebDriverFactory}. If an instance
45       * of the driver is already running then it will be re-used instead of
46       * creating a new instance.
47       *
48       * @return an instance of the driver
49       */
50      public static AtlassianWebDriver getDriver()
51      {
52          String browserProperty = WebDriverFactory.getBrowserProperty();
53          if (browserIsConfigured(browserProperty))
54          {
55              AtlassianWebDriver driver = drivers.get(browserProperty);
56              currentDriver = driver;
57              return driver;
58          }
59  
60          AtlassianWebDriver driver = null;
61          if (RemoteWebDriverFactory.matches(browserProperty))
62          {
63              log.info("Loading remote driver: " + browserProperty);
64              driver = RemoteWebDriverFactory.getDriver(browserProperty);
65          }
66          else
67          {
68              log.info("Loading local driver: " + browserProperty);
69              BrowserConfig browserConfig = AutoInstallConfiguration.setupBrowser();
70              driver = WebDriverFactory.getDriver(browserConfig);
71          }
72          drivers.put(browserProperty, driver);
73          currentDriver = driver;
74  
75          addShutdownHook(browserProperty, driver);
76          return driver;
77      }
78  
79      public static AtlassianWebDriver getCurrentDriver()
80      {
81          Preconditions.checkState(currentDriver != null, "The current driver has not been initialised");
82          return currentDriver;
83      }
84  
85      public static Supplier<AtlassianWebDriver> currentDriverSupplier()
86      {
87          return new Supplier<AtlassianWebDriver>()
88          {
89              @Override
90              public AtlassianWebDriver get()
91              {
92                  return getCurrentDriver();
93              }
94          };
95      }
96  
97      /**
98       * A manual shut down of the registered drivers. This basically resets the grid to a blank state. The shutdown hooks
99       * for the drivers that have been closed are also removed.
100      *
101      */
102     public static void shutdown()
103     {
104         for (Map.Entry<String,AtlassianWebDriver> driver: drivers.entrySet())
105         {
106             quit(driver.getValue());
107             removeHook(driver);
108         }
109         drivers.clear();
110         SHUTDOWN_HOOKS.clear();
111         currentDriver = null;
112     }
113 
114     private static void removeHook(Map.Entry<String, AtlassianWebDriver> driver)
115     {
116         WeakReference<Thread> hookRef = SHUTDOWN_HOOKS.get(driver.getKey());
117         final Thread hook = hookRef != null ? hookRef.get() : null;
118         if (hook != null)
119         {
120             Runtime.getRuntime().removeShutdownHook(hook);
121         }
122     }
123 
124     private static void quit(AtlassianWebDriver webDriver)
125     {
126         try
127         {
128             webDriver.quit();
129         }
130         catch (WebDriverException e)
131         {
132             onQuitError(webDriver, e);
133         }
134     }
135 
136 
137     private static boolean browserIsConfigured(String browserProperty)
138     {
139         return drivers.containsKey(browserProperty);
140     }
141 
142     private static void addShutdownHook(final String browserProperty, final WebDriver driver) {
143         final Thread quitter = new Thread()
144         {
145             @Override
146             public void run()
147             {
148                 log.debug("Running shut down hook for {}", driver);
149                 try
150                 {
151                     drivers.remove(browserProperty);
152                     if (driver.equals(currentDriver))
153                     {
154                         currentDriver = null;
155                     }
156                     log.info("Quitting {}", getUnderlyingDriver(driver));
157                     driver.quit();
158                     log.debug("Finished shutdown hook {}", this);
159                 }
160                 catch (NullPointerException e)
161                 {
162                     // SELENIUM-247: suppress this error (but log it) since it's likely to be due to a harmless
163                     // known issue where we're trying to clean up resources that the driver already cleaned up.
164                     onQuitError(driver, e);
165                 }
166                 catch (WebDriverException e)
167                 {
168                     onQuitError(driver, e);
169                 }
170             }
171         };
172         SHUTDOWN_HOOKS.put(browserProperty, new WeakReference<Thread>(quitter));
173         Runtime.getRuntime().addShutdownHook(quitter);
174     }
175 
176 
177     private static void onQuitError(WebDriver webDriver, Exception e)
178     {
179         // there is no sense propagating the exception, and in 99 cases out of 100, the browser is already dead if an
180         // exception happens
181         log.warn("Exception when trying to quit driver {}: {}", webDriver, e.getMessage());
182         log.debug("Exception when trying to quit driver - details", e);
183     }
184 
185 }