In the previous article How to use the Factory design pattern to create browser instances: the simple approach, you have learned how to implement it. I’ve named it “simple” because the code created runs only locally, which is the simplest and most elegant way of using Java to do it.
To make this post/tutorial as short as possible I will refer, through links, to the existing code implementation where you can access and understand more about the solution applied.
Local execution means the usage of the browser installed on the same machine you will run the tests, so we need the web browser and the browser drivers.
Remote execution means the execution of the tests in a different machine from the one you will trigger. This “machine” is actually any resource as a different real machine (bare-metal), a Docker container, or a remote service (E.g: SauceLabs, BrowserStack).
In the local execution, we directly use the browser instance. Don’t forget you need also the browser driver and it can be easily configured using the WebDriverManager library.
// setting up the browser driver with the WebDriverManager library WebDriverManager.getInstance(CHROME).setup(); // creating the local browser instance WebDriver driver = new ChromeDriver();
The WebDriver manager will set up the Chrome driver and Selenium will look a the local Google Chrome installation and will use it to run your tests.
But, for the remote execution, things are a little bit different. You will need to:
Similar to the How to use the Factory design pattern to create browser instances: the simple approach article shows how to create a factory class to manage different browsers to run locally, we have now to create a way to determine when to run locally and when to run in a remote machine.
This is the class diagram that we will work on.
IDriveManager is an interface that has the methods to implement in the manager classes. The manager classes have the implementation for the local, and now, remote execution.
The local execution is done by the method
createDriver() and the remote execution is done by the
getOptions() method. I will explain more about it later.
DriverFactory class is the one responsible to know if the execution will be either local or remote. We manage this through a property value. Based on the target environment we will instantiate either the
LocalDriverFactory or the
RemoteDriverFactory. This class implemented the interface
The LocalDriverFactory class is responsible to create the local browser instance using the
createDriver() method from the manager classes.
RemoteDriverFactory class is responsible to create the remote browser execution using the
getOptions() from the manager classes. The internal private method
createRemoteInstance() will connect to the remote machine before the creation of the browser instance.
I will explain here how to evolve code based on the simple approach. The following sub-items will explain it.
The first thing we need to create is a way to run the test using either a local or remote machine. For the remote execution, we will need to parameterize the URL and port we will connect our tests. We also need to add a way to say when we need to execute it as local or remote.
The approach I’ve created is to add a new grid.properties file to add the Selenium Grid URL and port
# grid url and port grid.url = localhost grid.port = 4444
Note: the file is named grid, as well as the properties because the way to run Selenium tests in different machines implies the Selenium Grid features.
We also need to update the general.properties file to have the local property to tell, somehow, we can execute it locally or remote. Line 2 shows the new property.
Finally, in the Configuration class, you will see the new association to the new properties created in the
general.properties. Also the load of the
grid.properties in the
@Config.Sources annotation from the Owner library.
The previous DriverFactory was managing the local execution. Now, to maintain a better naming consistency, we are changing it to manage the execution into local or remote. It has a simple
switch-case to instantiate the correct internal class.
Lines 21 to 23 show an internal enum named
Target which has
REMOTE as possible values.
Line 5 loads the target property from the
general.properties file and converts it to
Target enum, whereas line 8 applies a switch statement in the enum.
When the execution is from the local, the LocalDriverFactory class is in use to create the
WebDriver instance based on the browser set in
general.properties file. When the execution is remote the RemoteDriverFactory class is in use. The
LocalDriverFactory class is the same as the previous DriverFactory from the basic-example branch.
We now have a new class to instantiate the different browsers for the remote execution. I will assume here you are already familiar with Selenium Grid, so I will skip the explanation of the code that creates the
RemoteWebDriverInstance. If you need to learn more about it, please read the official Selenium Grid documentation and the code I’ve implemented for this solution.
Basically, we will have the same
switch-case strategy for each browser compared to the LocalDriverFactory, but instead of instantiating the local browser, we need to instantiate the remote one.
Each driver manager class will have, additionally, a new method to use when the execution is remote. As the
RemoteWebDriver does not accept a browser instance directly, but a MutableCapabilities class, we need to specify the correlated option class of the browser.
Each browser has its own
*Options class, like ChromeOptions, FirefoxOptions, and so on. Those classes exist for the remote execution and have pre-configured options as their identification plus methods that can extend their features. Internally the
*Options classes have a hierarchy dependency on the
What we need to do is to add the
getOptions() methods to each
DriverManager class. This method must instantiate the related browser option and can add more features.
You can see all the implemented
DriverManager classes in the manager package.
The execution to local or remote will is based on the
target property value in the general.properties file. We know that when the execution is
local the DriverFactory will use the LocalDriverFactory class to create the local browser instance and when the execution is
remote the DriverFactory will use the RemoteDriverFactory to create a remote browser instance.
Follow these steps:
general.propertiesfile located in
targetproperty value to
browserproperty value to any browser you have installed
The expected result is the test being executed in your local machine for your set browser. This is the same way we did in the previous post.
Now things get more interesting. We need to find a way to execute the test on a remote machine. As written before a remote machine can be a physical or virtual machine existent in your network or a cloud provider. To be able to run it on your machine right now Docker is one of your best choices.
The project has a docker-compose.yml file which will start a full Selenium Grid infrastructure containing the sessions, distributors, router, event bus, and the browsers. You can learn more about the Selenium Grid 4 on their official page and in their GitHub project.
As a pre-condition, you need to have Docker installed on your machine and then follow these steps:
http://localhost:4444to see the Selenium Grid page with the browsers available (Chrome, Edge, and Firefox)
general.propertiesfile, change the
targetattribute value to
src/main/java/resourcesalready have the values to connect to a local grid
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.
If you have any questions related to the implementation, or if something is not clear, please leave a comment or send me a message on my social networks.