The best way to create browser instances using the Factory Pattern, Java, and Selenium WebDriver
Tags: testing, java, webtesting, selenium
Published: 2022-08-20 09:41:19

## 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](http://www.eliasnogueira.com/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](http://www.eliasnogueira.com/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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7x9r3fpotgjnp8g7zz61.png)

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](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html) 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:

```java
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](https://github.com/bonigarcia/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](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/remote/AbstractDriverOptions.html) 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.

```java
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](https://github.com/eliasnogueira/selenium-java-browser-factory/blob/master/src/test/resources/general.properties#L2) 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.

```java
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](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/remote/AbstractDriverOptions.html) extends the [MutableCapabilities](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/MutableCapabilities.html) 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](https://github.com/eliasnogueira/selenium-java-browser-factory/blob/master/src/main/resources/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](https://github.com/eliasnogueira/selenium-java-browser-factory/blob/master/src/test/java/com/eliasnogueira/BaseWeb.java) 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

```java
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.

```java
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](https://github.com/eliasnogueira/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:

1. Start Docker
2. Navigate, through your Command-Line/Terminal app to the project root folder
3. Run `docker-compose up` command
4. Access `http://localhost:4444` to see the Selenium Grid page with the browsers available (Chrome, Edge, and Firefox)
5. In the `general.properties` file, change the `target` attribute value to `remote`
  * the `grid.properties` file in `src/main/java/resources` already have the values to connect to a local grid
6. 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