The best way to create browser instances using the Factory Pattern, Java, and Selenium WebDriver
Elias Nogueira
Posted on August 20, 2022
Introduction
This article belongs to a series of 3 posts explaining how to use the Factory Pattern to create different browser instances for either local or remote execution using Selenium WebDriver.
You can find the following previous article:
- How to use the Factory design pattern to create browser instances: the simple approach
- How to use the Factory design pattern to create browser instances: local and remote approach
This one will refer to some explanations and code from the previous one, but don’t be afraid, you will understand everything.
General explanation
Can you imagine you can implement a good approach to create multiple browser instances with only two classes?
The BrowserFactory and the power of enums
We already know that the Enum Types have constant and can have methods.
The BrowserFactory
class is an enum with the list of supported browsers (constants) for the implementation I’ve created: Chrome, Firefox, Edge, and Safari.
We can add an extra function on this enum with methods, but instead of creating the regular ones we will make each constant implement two methods: createDriver()
for the local execution and getOptions()
for the remote execution. In order to do so, we must implement abstract methods inside the enum. Take a look at the example:
public enum BrowserFactory {
CHROME {
@Override
public WebDriver createDriver() {
WebDriverManager.getInstance(DriverManagerType.CHROME).setup();
return new ChromeDriver(getOptions());
}
@Override
public ChromeOptions getOptions() {
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--start-maximized");
chromeOptions.addArguments("--disable-infobars");
chromeOptions.addArguments("--disable-notifications");
chromeOptions.setHeadless(configuration().headless());
return chromeOptions;
}
};
public abstract WebDriver createDriver();
public abstract AbstractDriverOptions<?> getOptions();
}
On lines 23 and 24 we have two abstract methods. Each enum constant needs to implement the abstract method.
On lines 5 to 9, there’s the implementation of the createDriver()
method creating a local browser instance using the WebDriverManager library. Note that the new browser instance is using the getOptions()
and I will explain it in a minute.
You can see that line 24 has the AbstractDriverOptions with an unknown generic, placed with ?
as the wildcard. It returns this class because each internal Selenium WebDriver remote browser implementation extends this class, making it easier to set the correct object for the remote instance creation. You can see this by accessing the AbstractDriverOptions Javadoc in the section “Direct Known Subclasses“
Lines 12 to 20 show the specific ChromeOptions
we must set for the remote execution, as the remote class is called “option” which must be set.
And why we have the getOptions()
method being used in the createDriver()
? To automatically set any option we want to. The option classes do not need the browser driver set as needed in the local execution.
The full BrowserDriver implementation can be found at https://github.com/eliasnogueira/selenium-java-browser-factory/blob/master/src/main/java/com/eliasnogueira/driver/BrowserFactory.java
The DriverFactory class
This class is responsible to manage the local or remote instances because we need different code approaches using Selenium WebDriver. We have two main methods: the createInstance()
for the local execution and the createRemoveInstance()
for the remote execution.
Creating the local instance
The local instance means that we will run the tests locally in the browser installed on the machine.
The method createInstance()
has the browser as the method parameter that the BaseTest
class will use to determine which browser will be instantiated.
The browser value attribute is transformed in the Target
enum and then used in the switch-case.
public class DriverFactory {
public WebDriver createInstance(String browser) {
Target target = Target.valueOf(configuration().target().toUpperCase());
WebDriver webdriver;
switch (target) {
case LOCAL:
webdriver = BrowserFactory.valueOf(browser.toUpperCase()).createDriver();
break;
case REMOTE:
webdriver = createRemoteInstance(BrowserFactory.valueOf(browser.toUpperCase()).getOptions());
break;
default:
throw new TargetNotValidException(target.toString());
}
return webdriver;
}
}
When the target is LOCAL
the BrowserFactory
class will call the createDriver()
method for the browser we have set. This approach removes the necessity to have the switch-case
for each browser as the browser value will be transformed in the enum constant. Less code, more readable code!
You can see this happening on lines 8 to 10.
Remember that the browser to use locally is set in the general.properties class.
Creating the Remote instance
It works similar to the local approach when the code determines if the execution is local or remote. Line 12, in the previous code snippet, shows that the BrowserFactory
will call the getOptions()
method based on the browser we have set. With this, we have the browser options object. Now it’s time to create the real remote instance.
private RemoteWebDriver createRemoteInstance(MutableCapabilities capability) {
RemoteWebDriver remoteWebDriver = null;
try {
String gridURL = String.format("http://%s:%s", configuration().gridUrl(), configuration().gridPort());
remoteWebDriver = new RemoteWebDriver(new URL(gridURL), capability);
} catch (java.net.MalformedURLException e) {
logger.log(Level.SEVERE, "Grid URL is invalid or Grid is not available");
logger.log(Level.SEVERE, String.format("Browser: %s", capability.getBrowserName()), e);
} catch (IllegalArgumentException e) {
logger.log(Level.SEVERE, String.format("Browser %s is not valid or recognized", capability.getBrowserName()), e);
}
return remoteWebDriver;
}
The createRemoteInstance()
method expects a MutableCapability
object, which is the same as the browser option object because the AbstractDriverOptions extends the MutableCapabilities class.
To create a remote browser instance we must use the RemoteWebDriver
class, adding as a parameter the target remote machine (or a grid) and the capability, which is the browser with the options.
On line 4 the URL for the remote machine is created based on the information we have in the grid.properties file. On line 6 the remote instance is created. The rest of the code is the exception handler, necessary to know what happened if any problem occurs during the remote instance creation.
How to associate this with the test?
First, it’s a good practice to have a BaseTest class managing the general pre and post-conditions. The BaseWeb class has the pre-conditions, as a JUnit @BeforeEach
annotation, the following:
- Line 8: getting the configurations, which means all the information from the properties file
- Line 10: the creation of the browser instance based on the browser set in the properties file
- Line 11: setting the URL to start the test
public class BaseWeb {
protected WebDriver driver;
protected Configuration configuration;
@BeforeEach
public void preCondition() {
configuration = configuration();
driver = new DriverFactory().createInstance(configuration().browser());
driver.get(configuration().url());
}
@AfterEach
public void postCondition() {
driver.quit();
}
}
The main part related to this approach is in line 10, where the DriverFactory
class will know, internally, if the execution is local or remote based on the value set for the target
property set in the general.properties
file. The browser was also set in this file but through the browser
property will be used to create the local browser instance or the remote instance.
The tests must extends the BaseWeb
to work properly.
class BookRoomWebTest extends BaseWeb {
@Test
void bookARoom() {
driver.navigate().to(configuration.url() +
"how-to-create-lean-test-automation-architecture-for-web-using-java-libraries");
assertThat(driver.findElement(By.cssSelector(".hestia-title.title-in-content")).getText()).
isEqualTo("How to create Lean Test Automation Architecture for Web using Java libraries");
}
}
How to run this project in remote machines
If you would like to give it a try the complete project selenium-java-browser-factory has a docker-compose file that will start a Selenium 4 Grid with Chrome, Firefox, and Edge browsers. So you will be able to run remote instances.
To exercise this, please follow these steps:
- Start Docker
- Navigate, through your Command-Line/Terminal app to the project root folder
- Run
docker-compose up
command - Access
http://localhost:4444
to see the Selenium Grid page with the browsers available (Chrome, Edge, and Firefox) - In the
general.properties
file, change thetarget
attribute value toremote
- the
grid.properties
file insrc/main/java/resources
already have the values to connect to a local grid
- the
- Run the
BookARoomWebTest
- you can go to the Selenium Grid dashboard and click on the Sessions tab
- as soon as the test starts a session related to the browser in use will be listed
The complete example
You can navigate to the following GitHub repo to see a complete and functional example in the branch local-remote-example
. Spare some time to look at the new classes added and understand how it was structured.
https://github.com/eliasnogueira/selenium-java-browser-factory/tree/basic-example
Posted on August 20, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
August 20, 2022