Skip to main content
SeleniumDecoded

Migrating from Selenium 3 to 4

A step-by-step upgrade guide: dependency changes, the capabilities rewrite, removed APIs, Duration-based timeouts, Service objects, and a checklist for suites written against Selenium 3.

Selenium 4 Stable Updated 9 Sept 2026 · Verified against Selenium 4.48.0

Selenium 3.141.59 was the last 3.x release, in 2018. Suites still running on it exist in every large company, and upgrading them is one of the most common tasks handed to a new automation engineer. The good news: most 3.x code compiles and runs unchanged on 4.x. The bad news: the parts that break are exactly the parts that were never idiomatic to begin with.

Step 1: Bump the Dependency

Move straight to the current version. Do not stop at 4.0; the 4.0 to 4.48 changes are additive and you get Selenium Manager, which removes an entire class of upgrade problems.

Dependency update
Selenium 4 Stable
pom.xml
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.48.0</version>
</dependency>
<!-- Remove these if present: they are no longer needed -->
<!-- io.github.bonigarcia:webdrivermanager (Selenium Manager replaces it) -->
<!-- org.seleniumhq.selenium:htmlunit-driver (not shipped with Selenium 4) -->
requirements.txt
selenium>=4.48
# Remove: webdriver-manager (Selenium Manager replaces it)
# Ensure: Python 3.10 or newer (3.9 support ended in Selenium 4.37)
package.json
{
"dependencies": {
"selenium-webdriver": "^4.48.0"
}
}
// Remove chromedriver / geckodriver npm packages; Selenium Manager handles drivers.
.csproj
<PackageReference Include="Selenium.WebDriver" Version="4.48.0" />
<PackageReference Include="Selenium.Support" Version="4.48.0" />
<!-- Remove Selenium.WebDriver.ChromeDriver and similar driver packages -->
<!-- Assemblies are strongly signed from 4.44 -->

Runtime floors: Java 11, Python 3.10, .NET 8 or .NET Framework 4.6.2 via netstandard2.0, Node.js 18.

Step 2: Replace DesiredCapabilities with Options

This is the change that breaks the most suites. Selenium 4 speaks only the W3C protocol, and W3C rejects unknown top-level capability keys. Cloud providers moved their custom keys under vendor prefixes such as sauce:options or bstack:options.

Before and after: capabilities
Selenium 4 Stable
// SELENIUM 3
DesiredCapabilities caps = DesiredCapabilities.chrome();
caps.setCapability("version", "92");
caps.setCapability("platform", "Windows 10");
caps.setCapability("build", "nightly-123"); // vendor key at top level: rejected by W3C
WebDriver driver = new RemoteWebDriver(gridUrl, caps);
// SELENIUM 4
ChromeOptions options = new ChromeOptions();
options.setBrowserVersion("92");
options.setPlatformName("Windows 10");
Map<String, Object> cloudOptions = new HashMap<>();
cloudOptions.put("build", "nightly-123");
options.setCapability("cloud:options", cloudOptions); // vendor prefix
WebDriver driver = new RemoteWebDriver(gridUrl, options);
# SELENIUM 3
caps = DesiredCapabilities.CHROME.copy()
caps["version"] = "92"
caps["platform"] = "Windows 10"
caps["build"] = "nightly-123" # rejected by W3C
driver = webdriver.Remote(command_executor=grid_url, desired_capabilities=caps)
# SELENIUM 4 (desired_capabilities argument was removed in 4.10)
options = webdriver.ChromeOptions()
options.browser_version = "92"
options.platform_name = "Windows 10"
options.set_capability("cloud:options", {"build": "nightly-123"})
driver = webdriver.Remote(command_executor=grid_url, options=options)
// SELENIUM 3
const caps = { browserName: 'chrome', version: '92', platform: 'Windows 10', build: 'nightly-123' };
const driver = new Builder().usingServer(gridUrl).withCapabilities(caps).build();
// SELENIUM 4
const chrome = require('selenium-webdriver/chrome');
const options = new chrome.Options()
.setBrowserVersion('92')
.setPlatformName('Windows 10')
.set('cloud:options', { build: 'nightly-123' });
const driver = await new Builder().usingServer(gridUrl).forBrowser('chrome').setChromeOptions(options).build();
// SELENIUM 3
var caps = new DesiredCapabilities("chrome", "92", new Platform(PlatformType.Windows));
caps.SetCapability("build", "nightly-123");
var driver = new RemoteWebDriver(gridUri, caps);
// SELENIUM 4
var options = new ChromeOptions { BrowserVersion = "92", PlatformName = "Windows 10" };
options.AddAdditionalOption("cloud:options", new Dictionary<string, object> { ["build"] = "nightly-123" });
// AddAdditionalCapability was replaced by AddAdditionalOption
var driver = new RemoteWebDriver(gridUri, options);

If you must merge capabilities, remember Java’s merge no longer mutates: write options = options.merge(caps).

Step 3: Timeouts and Waits Take Duration

Java removed the (long, TimeUnit) overloads. Anything that compiled with a warning in 4.0 fails to compile now.

Duration-based timeouts
Selenium 4 Stable
import java.time.Duration;
// SELENIUM 3
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);
WebDriverWait wait = new WebDriverWait(driver, 10);
// SELENIUM 4
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
# Python signatures did not change; seconds are still floats
driver.implicitly_wait(10)
driver.set_page_load_timeout(30)
wait = WebDriverWait(driver, 10)
# Removed in 4.x: driver.find_element_by_id(...) and friends (deprecated in 4.0, removed 4.3)
from selenium.webdriver.common.by import By
driver.find_element(By.ID, "username")
// Timeouts are set through manage().setTimeouts with milliseconds
await driver.manage().setTimeouts({ implicit: 10000, pageLoad: 30000, script: 30000 });
// Explicit waits unchanged
await driver.wait(until.elementLocated(By.id('result')), 10000);
// TimeSpan was already the C# convention; unchanged
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(30);
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
// ExpectedConditions moved to the DotNetSeleniumExtras.WaitHelpers package,
// or write lambdas: wait.Until(d => d.FindElement(By.Id("result")).Displayed);

Step 4: Driver Paths Become Service Objects (or Disappear)

Driver construction
Selenium 4 Stable
// SELENIUM 3
System.setProperty("webdriver.chrome.driver", "/opt/drivers/chromedriver");
WebDriver driver = new ChromeDriver();
// SELENIUM 4: preferred, let Selenium Manager resolve the driver
WebDriver driver = new ChromeDriver();
// SELENIUM 4: explicit path when you must (air-gapped CI)
ChromeDriverService service = new ChromeDriverService.Builder()
.usingDriverExecutable(new File("/opt/drivers/chromedriver"))
.build();
WebDriver driver = new ChromeDriver(service, new ChromeOptions());
# SELENIUM 3
driver = webdriver.Chrome(executable_path="/opt/drivers/chromedriver")
# SELENIUM 4: preferred
driver = webdriver.Chrome()
# SELENIUM 4: explicit path
from selenium.webdriver.chrome.service import Service
service = Service(executable_path="/opt/drivers/chromedriver")
driver = webdriver.Chrome(service=service)
// SELENIUM 3: required the chromedriver npm package on PATH
// SELENIUM 4: preferred
const driver = await new Builder().forBrowser('chrome').build();
// Explicit path
const chrome = require('selenium-webdriver/chrome');
const service = new chrome.ServiceBuilder('/opt/drivers/chromedriver');
const driver = await new Builder().forBrowser('chrome').setChromeService(service).build();
// SELENIUM 3
var driver = new ChromeDriver(@"C:\drivers");
// SELENIUM 4: preferred
var driver = new ChromeDriver();
// Explicit path
var service = ChromeDriverService.CreateDefaultService(@"C:\drivers", "chromedriver.exe");
var driver = new ChromeDriver(service, new ChromeOptions());

Step 5: Fix the Long Tail

Search your codebase for these and replace them:

Selenium 3Selenium 4
driver.findElementById("x") (Java)driver.findElement(By.id("x"))
driver.find_element_by_css_selector(...) (Python)driver.find_element(By.CSS_SELECTOR, ...)
new Actions(driver).moveToElement(e).build().perform()build() is optional; perform() alone works
driver.executeScript("window.open()") to open tabsdriver.switchTo().newWindow(WindowType.TAB)
FirefoxOptions.setLegacy(true)Delete; only geckodriver is supported
Augmenter to get screenshots from RemoteWebDriverNot needed; RemoteWebDriver implements TakesScreenshot
FirefoxProfile + FirefoxBinary (Python)options.binary_location, options.set_preference
driver.get_log("browser") (Python)BiDi console handlers; classic log API is Chromium-only
ExpectedConditions in OpenQA.Selenium.Support.UI (C#)SeleniumExtras.WaitHelpers package or lambdas
htmlunit-driverNot bundled; add separately or use headless Chrome

Step 6: Reconsider What 3.x Made You Write

An upgrade is a chance to delete code, not just port it:

  • Driver download utilities (WebDriverManager, shell scripts, checked-in binaries): Selenium Manager replaces them.
  • JavaScript scroll-and-click helpers: usually written to dodge Selenium 3 click failures; try plain click() again, it improved.
  • Custom “wait for console error” hacks that polled driver.manage().logs(): replace with BiDi addJavaScriptErrorHandler.
  • Thread.sleep: was never right; see Explicit Waits.

Migration Checklist

  1. Upgrade the dependency to the latest 4.x and fix compile errors (Java, C#) or run the suite and fix TypeError / AttributeError (Python, JavaScript).
  2. Rewrite every DesiredCapabilities block as Options; move vendor keys under a prefix.
  3. Replace TimeUnit timeouts with Duration (Java).
  4. Delete driver path management and the libraries that did it.
  5. Run the whole suite in CI twice. Selenium 4’s W3C actions and stricter element interactability can surface real timing bugs the 3.x driver masked.
  6. Enable BiDi on one test class and start capturing JavaScript errors; you will find product bugs immediately.

Summary

  • The dependency bump is trivial; the capabilities rewrite is not. Budget time for cloud provider keys.
  • Java timeouts require Duration; Python lost executable_path and find_element_by_*.
  • Let Selenium Manager handle drivers and remove the tooling that used to.
  • Treat the migration as a cleanup: most Selenium 3 workarounds have a native Selenium 4 replacement.

Related lessons