I guess most people who are involved in the automated testing of web applications to some extent, came up to the managing of browsers and their drivers while working with Selenium-based test frameworks. There might be a lot of solutions how to make their life easier. Some out-of-the-box frameworks (like Selenide) have already implemented WebDriver managers internally, however, in some cases, we need to handle it by ourselves.
The most known and possible solution for such cases is creating a custom WebDriver manager based on the Factory design pattern. There are lots of different implementations on GitHub and you can easily Google them, but today I’d want to share one of the latest experiments that I did (inspired by this solution with enums). So, here we go…
Challenge: To implement a Browser Manager (i.e., WebDriver manager) which simplifies running tests on different browsers, including parallel execution.
First of all, let’s get rid of annoying drivers binaries management! As you know, each browser has its own driver binary which should be downloaded and its location should be added to the classpath
. The WebDriverManager project provides the API for downloading necessary drivers and configuring system properties automatically. All you need is love to add its dependency (through Maven or Gradle) and call a fabric method relevant to a needed browser:
ChromeDriverManager.getInstance().setup();
driver = new ChromeDriver();
It’s pretty easy, isn’t it? It supports Firefox, Chrome, Opera, Internet Explorer, Edge and PhantomJS browsers for now, but I’m going to add the HtmlUnit browser in the final solution also. The WebDriverManager
mentioned above allows us to also specify architecture, driver version and several other parameters if necessary).
Following the code above and using the example for enums, let’s create our own Browser enum
with all browsers listed above:
public enum Browser {
FIREFOX {
@Override
public WebDriver initialize(DesiredCapabilities capabilities) {
synchronized (BrowserManager.class) {
FirefoxDriverManager.getInstance().setup();
return new FirefoxDriver(capabilities);
}
}
},
CHROME {
@Override
public WebDriver initialize(DesiredCapabilities capabilities) {
synchronized (BrowserManager.class) {
ChromeDriverManager.getInstance().setup();
return new ChromeDriver(capabilities);
}
}
},
IE {
},
EDGE {
},
OPERA {
},
PHANTOMJS {
},
HTMLUNIT {
@Override
public WebDriver initialize(DesiredCapabilities capabilities) {
return new HtmlUnitDriver(capabilities);
}
};
public abstract WebDriver initialize(DesiredCapabilities capabilities);
}
}
The logic of the whole enum
is incredibly simple: there is the abstract
initialize()
method which must be implemented for each member of the enum
. Each overridden initialize()
method (excepts for the HtmlUnit
driver) sets up an instance of a required driver manager class and then returns a required driver after all dependencies are downloaded and configured. In order to make the WebDriverManager
API more thread-safe while running tests in different browsers in parallel, I also put all code related to the BrowserManager.class
inside the synchronized {..}
block.
The final step before moving forward to the fabric usage in the framework is to create the fabric. Let’s make the new BrowserProvider
class:
public class BrowserProvider {
public static WebDriver createDriver(Browser browser, DesiredCapabilities capabilities) {
capabilities.setBrowserName(browser.toString().toLowerCase());
return browser.initialize(capabilities);
}
public static WebDriver createDriver(Browser browser) {
return createDriver(browser, new DesiredCapabilities());
}
public static RemoteWebDriver createDriver
(URL hubUrl, Browser browser, DesiredCapabilities capabilities) {
capabilities.setBrowserName(browser.toString().toLowerCase());
return new RemoteWebDriver(hubUrl, capabilities);
}
public static RemoteWebDriver createDriver(URL hubUrl, Browser browser) {
return createDriver(hubUrl, browser, new DesiredCapabilities());
}
}
There are two methods for the RemoteWebDriver
creation and two similar ones for the local test tun. All of them take a Browser as a parameter which can be passed from a test (or any other setup class that you may use). Optionally, methods can take DesiredCapabilities
.
Now we can use the fabric from the test code:
public class LocalTestsExample {
private WebDriver driver;
@BeforeClass
@Parameters("browser")
public void setUpClass(Browser browser) {
driver = BrowserProvider.createDriver(browser);
}
@Test
public void CodeIconLoadingTest() { }
@AfterClass
public void tearDownTest() { }
}
As you have noticed, I used TestNG
annotations inside the test code. It’s because I prefer this test framework from the easy parametrization point of view, so it’s suitable for demonstrating a parallel test execution in different browsers. Here is how the typical TestNG file may look like:
="1.0"="UTF-8"
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="TestSuite" parallel="tests">
<test name="EdgeTest">
<parameter name="browser" value="EDGE" />
<classes>
<class name="LocalTestsExample"/>
</classes>
</test>
<test name="FirefoxTest">
<parameter name="browser" value="FIREFOX" />
<classes>
<class name="LocalTestsExample"/>
</classes>
</test>
</suite>
When you run your test through the above-mentioned TestNG file, you’ll see Firefox and Edge browsers starting and running tests in parallel. You can easily add any other of the supported browsers and run them in parallel as well as separately. Please note that the very first run for each browser might take more time – WebDriverManager
will download necessary binaries to the local Maven repository.
The complete project code is available on GitHub, so feel free to add it to your project, use and / or modify according to your needs, if necessary. Any suggestions are welcome!