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.