How To Upgrade to Selenium 4

Posted Oct 12th, 2021

The Selenium team has spent a good amount of time making the upgrade process as painless as possible. A few things have been deprecated, so you might hit a couple of issues while upgrading, especially if you have built custom functionalities in your testing framework. This guide will show you how to move from Selenium 3 to Selenium 4.

Upgrading to Selenium 4 should be a painless process if you are using one of the officially supported languages (Ruby, JavaScript, C#, Python, and Java). There might be some cases where a few issues can happen, and this guide will help you to sort them out. We will go through the steps to upgrade your project dependencies and understand the major deprecations and changes the new version upgrade brings.

These are the steps we will follow to upgrade to Selenium 4:

Note: while Selenium 3.x versions were being developed, support for the W3C WebDriver standard was implemented. Both this new protocol and the legacy JSON Wire Protocol was supported. Around version 3.11, Selenium code became compliant with the Level 1 W3C Recommendation. W3C compliant code in the latest version of Selenium 3 will work as expected in Selenium 4.

Preparing our test code

Selenium 4 removes support for the legacy protocol and uses the W3C WebDriver standard by default under the hood. For most things, this implementation will not affect end users. The major exceptions are Capabilities and the Actions class.

Capabilities

If the test capabilities are not structured to be W3C compliant, may cause a session to not be started. Here is the list of W3C WebDriver standard capabilities:

browserName
browserVersion (replaces version)
platformName (replaces platform)
acceptInsecureCerts
pageLoadStrategy
proxy
timeouts
unhandledPromptBehavior

An up-to-date list of standard capabilities can be found at W3C WebDriver.

Any capability that is not contained in the list above, needs to include a vendor prefix. This applies to browser specific capabilities as well as Sauce Labs specific capabilities. For example, if we use the build and name capabilities in our tests, we need to wrap them in a sauce:options block (a complete example is shown below).

From DesiredCapabilities to Browser Options

The use of browser Options instead of static browser methods of DesiredCapabilities has been suggested since Selenium 3.8. Those static methods have been removed in Selenium 4. This means that DesiredCapabilities.chrome() or DesiredCapabilities.firefox() and similar are not present anymore. See the examples below to migrate from those static methods to browser Options.

For example:

DesiredCapabilities caps = DesiredCapabilities.chrome();
DesiredCapabilities caps = DesiredCapabilities.edge();
DesiredCapabilities caps = DesiredCapabilities.firefox();
DesiredCapabilities caps = DesiredCapabilities.internetExplorer();
DesiredCapabilities caps = DesiredCapabilities.safari();

Are replaced by:

ChromeOptions browserOptions = new ChromeOptions();
EdgeOptions browserOptions = new EdgeOptions();
FirefoxOptions browserOptions = new FirefoxOptions();
InternetExplorerOptions browserOptions = new InternetExplorerOptions();
SafariOptions browserOptions = new SafariOptions();

Using browser Options simplifies the configuration needed to start a new session, allows setting browser-specific settings (like headless in Chrome), and reduces the chances of browser misconfiguration.

A complete example

Following, we can see a code block with the old usage of capability names and DesiredCapabilities. Followed by a block that shows how the code needs to be updated.

The example is shown in the different official languages supported by Selenium. When browser Options are preferred when available, platform and version are replaced by platformName and browserVersion, and Sauce Labs specific capabilities are placed inside a sauce:options block.

Java

Before:

DesiredCapabilities caps = DesiredCapabilities.firefox();
caps.setCapability("platform", "Windows 10");
caps.setCapability("version", "92");
caps.setCapability("build", myTestBuild);
caps.setCapability("name", myTestName);
WebDriver driver = new RemoteWebDriver(new URL(sauceUrl), caps);

After:

FirefoxOptions browserOptions = new FirefoxOptions();
browserOptions.setPlatformName("Windows 10");
browserOptions.setBrowserVersion("92");
Map<String, Object> sauceOptions = new HashMap<>();
sauceOptions.put("build", myTestBuild);
sauceOptions.put("name", myTestName);
browserOptions.setCapability("sauce:options", sauceOptions);
WebDriver driver = new RemoteWebDriver(new URL(sauceUrl), browserOptions);

JavaScript

Before:

caps = {};
caps['browserName'] = 'Firefox';
caps['platform'] = 'Windows 10';
caps['version'] = '92';
caps['build'] = myTestBuild;
caps['name'] = myTestName;

After:

capabilities = {
  browserName: 'firefox',
  browserVersion: '92',
  platformName: 'Windows 10',
  'sauce:options': {
     build: myTestBuild,
     name: myTestName,
  }
}

C#

Before:

DesiredCapabilities caps = new DesiredCapabilities();
caps.SetCapability("browserName", "firefox");
caps.SetCapability("platform", "Windows 10");
caps.SetCapability("version", "92");
caps.SetCapability("build", myTestBuild);
caps.SetCapability("name", myTestName);
var driver = new RemoteWebDriver(new Uri(SauceURL), capabilities);

After:

var browserOptions = new FirefoxOptions();
browserOptions.PlatformName = "Windows 10";
browserOptions.BrowserVersion = "92";
var sauceOptions = new Dictionary<string, object>();
sauceOptions.Add("build", myTestBuild);
sauceOptions.Add("name", myTestName);
browserOptions.AddAdditionalOption("sauce:options", sauceOptions);
var driver = ​​new RemoteWebDriver(new Uri(SauceURL), options);

Ruby

Before:

caps = Selenium::WebDriver::Remote::Capabilities.firefox
caps[:platform] = 'Windows 10'
caps[:version] = '92'
caps[:build] = my_test_build
caps[:name] = my_test_name
driver = Selenium::WebDriver.for :remote, url: sauce_url, desired_capabilities: caps

After:

options = Selenium::WebDriver::Options.firefox
options.browser_version = 'latest'
options.platform_name = 'Windows 10'
sauce_options = {}
sauce_options[:build] = my_test_build
sauce_options[:name] = my_test_name
options.add_option('sauce:options', sauce_options)
driver = Selenium::WebDriver.for :remote, url: sauce_url, capabilities: options

Python

Before:

caps = {}
caps['browserName'] = 'firefox'
caps['platform'] = 'Windows 10'
caps['version'] = '92'
caps['build'] = my_test_build
caps['name'] = my_test_name
driver = webdriver.Remote(sauce_url, desired_capabilities=caps)

After:

from selenium.webdriver.firefox.options import Options as FirefoxOptions
options = FirefoxOptions()
options.browser_version = '92'
options.platform_name = 'Windows 10'
sauce_options = {}
sauce_options['build'] = my_test_build
sauce_options['name'] = my_test_name
options.set_capability('sauce:options', sauce_options)
driver = webdriver.Remote(sauce_url, options=options)

For more combinations and examples, check the Sauce Labs platform configurator.

Find element(s) utility methods in Java

The utility methods to find elements in the Java bindings (FindsBy interfaces) have been removed as they were meant for internal use only. The following code samples explain this better.

Before:

driver.findElementByClassName("className");
driver.findElementByCssSelector(".className");
driver.findElementById("elementId");
driver.findElementByLinkText("linkText");
driver.findElementByName("elementName");
driver.findElementByPartialLinkText("partialText");
driver.findElementByTagName("elementTagName");
driver.findElementByXPath("xPath");

After:

driver.findElement(By.className("className"));
driver.findElement(By.cssSelector(".className"));
driver.findElement(By.id("elementId"));
driver.findElement(By.linkText("linkText"));
driver.findElement(By.name("elementName"));
driver.findElement(By.partialLinkText("partialText"));
driver.findElement(By.tagName("elementTagName"));
driver.findElement(By.xpath("xPath"));

All the findElements* have been removed as well.

Before:

driver.findElementsByClassName("className");
driver.findElementsByCssSelector(".className");
driver.findElementsById("elementId");
driver.findElementsByLinkText("linkText");
driver.findElementsByName("elementName");
driver.findElementsByPartialLinkText("partialText");
driver.findElementsByTagName("elementTagName");
driver.findElementsByXPath("xPath");

After:

driver.findElements(By.className("className"));
driver.findElements(By.cssSelector(".className"));
driver.findElements(By.id("elementId"));
driver.findElements(By.linkText("linkText"));
driver.findElements(By.name("elementName"));
driver.findElements(By.partialLinkText("partialText"));
driver.findElements(By.tagName("elementTagName"));
driver.findElements(By.xpath("xPath"));

Upgrading dependencies

Check the subsections below to install Selenium 4 and have your project dependencies upgraded.

Java

The process of upgrading Selenium depends on which build tool is being used. We will cover the most common ones for Java, which are Maven and Gradle. The minimum Java version required is still 8.

Maven

Before:

<dependencies>
	<!-- more dependencies ... -->
	<dependency>
    	<groupId>org.seleniumhq.selenium</groupId>
    	<artifactId>selenium-java</artifactId>
    	<version>3.141.59</version>
	</dependency>
	<!-- more dependencies ... -->
</dependencies>

After:

<dependencies>
	<!-- more dependencies ... -->
	<dependency>
    	<groupId>org.seleniumhq.selenium</groupId>
    	<artifactId>selenium-java</artifactId>
    	<version>4.0.0</version>
	</dependency>
	<!-- more dependencies ... -->
</dependencies>

After making the change, you could execute mvn clean compile on the same directory where the pom.xml file is.

Gradle

Before:

plugins {
	id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
	mavenCentral()
}

dependencies {
	testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
	testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
	implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'
}

test {
	useJUnitPlatform()

}

After:

plugins {
	id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
	mavenCentral()
}

dependencies {
	testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
	testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
	implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '4.0.0'
}

test {
	useJUnitPlatform()
}

After making the change, you could execute ./gradlew clean build
on the same directory where the build.gradle file is.

To check all the Java releases, you can head to MVNRepository.

C#

The place to get updates for Selenium 4 in C# is NuGet. Under the Selenium.WebDriver package you can get the instructions to update to the latest version. Inside of Visual Studio, through the NuGet Package Manager you can execute:

PM> Install-Package Selenium.WebDriver -Version 4.0.0

Python

The most important change to use Python is the minimum required version. Selenium 4 will require a minimum Python 3.7 or higher. More details can be found at the Python Package Index. To upgrade from the command line, you can execute:

pip install selenium==4.0.0

Ruby

The update details for Selenium 4 can be seen at the selenium-webdriver gem in RubyGems. To install the latest version, you can execute:

gem install selenium-webdriver

To add it to your Gemfile:

gem 'selenium-webdriver', '~> 4.0.0'

JavaScript

The selenium-webdriver package can be found at the Node package manager, npmjs. Selenium 4 can be found here. To install it, you could either execute:
npm install selenium-webdriver

Or, update your package. json and run npm install:

{
  "name": "selenium-tests",
  "version": "1.0.0",
  "dependencies": {
	"selenium-webdriver": "^4.0.0"
  }
}

Potential Errors and Deprecation Messages

Here is a set of code examples that will help to overcome the deprecation messages you might encounter after upgrading to Selenium 4.

Java

Waits and Timeout

The parameters received in Timeout have switched from expecting (long time, TimeUnit unit) to expect (Duration duration).

Before:

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.manage().timeouts().setScriptTimeout(2, TimeUnit.MINUTES);
driver.manage().timeouts().pageLoadTimeout(10, TimeUnit.SECONDS);

After:

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().timeouts().scriptTimeout(Duration.ofMinutes(2));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(10));

Waits are also expecting different parameters now. WebDriverWait is now expecting a Duration instead of a long for timeout in seconds and milliseconds. The withTimeout and pollingEvery utility methods from FluentWait have switched from expecting (long time, TimeUnit unit) to expect (Duration duration).

Before:

new WebDriverWait(driver, 3)
.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#id")));

Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
  .withTimeout(30, TimeUnit.SECONDS)
  .pollingEvery(5, TimeUnit.SECONDS)
  .ignoring(NoSuchElementException.class);

After:

new WebDriverWait(driver, Duration.ofSeconds(3))
.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#id")));

Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
  .withTimeout(Duration.ofSeconds(30))
  .pollingEvery(Duration.ofSeconds(5))
  .ignoring(NoSuchElementException.class);

Merging Capabilities is No Longer Changing the Calling Object

It was possible to merge a different set of capabilities into another set, and it was mutating the calling object. Now, the result of the merge operation needs to be assigned.

Before:

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("platformVersion", "Windows 10");
FirefoxOptions options = new FirefoxOptions();
options.setHeadless(true);
options.merge(capabilities);

As a result, the options object was getting modified.

After:

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("platformVersion", "Windows 10");
FirefoxOptions options = new FirefoxOptions();
options.setHeadless(true);
options = options.merge(capabilities);

The result of the merge call needs to be assigned to an object.

Firefox Legacy

Before GeckoDriver was around, the Selenium project had a driver implementation to automate Firefox (version <48). However, this implementation is not needed anymore as it does not work in recent versions of Firefox. To avoid major issues when upgrading to Selenium 4, the setLegacy option will be shown as deprecated. The recommendation is to stop using the old implementation and rely only on GeckoDriver. The following code will show the setLegacy line deprecated after upgrading.

FirefoxOptions options = new FirefoxOptions();
options.setLegacy(true);

BrowserType

The BrowserType interface has been around for a long time, however it is getting deprecated in favor of the new Browser interface.

Before:

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("browserVersion", "92");
capabilities.setCapability("browserName", BrowserType.FIREFOX);

After:

MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability("browserVersion", "92");
capabilities.setCapability("browserName", Browser.FIREFOX);

C#

AddAdditionalCapability is deprecated. Instead, AddAdditionalOption is recommended. Here is an example showing this:

Before:

var browserOptions = new ChromeOptions();
browserOptions.PlatformName = "Windows 10";
browserOptions.BrowserVersion = "latest";
var sauceOptions = new Dictionary<string, object>();
browserOptions.AddAdditionalCapability("sauce:options", sauceOptions, true);

After:

var browserOptions = new ChromeOptions();
browserOptions.PlatformName = "Windows 10";
browserOptions.BrowserVersion = "latest";
var sauceOptions = new Dictionary<string, object>();
browserOptions.AddAdditionalOption("sauce:options", sauceOptions);

Summary

We went through the major changes to be taken into consideration when upgrading to Selenium 4. Covering the different aspects to cover when test code is prepared for the upgrade, including suggestions on how to prevent potential issues that can show up when using the new version of Selenium. To finalize, we also covered a set of possible issues that you can bump into after upgrading, and we shared potential fixes for those issues.

Be sure to check out all of our Selenium 4 resources in this comprehensive guide to the newest update to the most tool for automating interaction with web browsers.


Written by

Diego Molina and TItus Fortner


Topics

SeleniumAutomated testing