ScreenshotNeo

BlogHow-to

How to Migrate to Selenium 4

Move a Selenium 3 suite to Selenium 4 with a low-risk checklist for dependencies, W3C capabilities, binding changes, and test failures.

By the ScreenshotNeo team4 October 20268 min read

To migrate to Selenium 4, first check your language binding, runtime, and driver setup; update Selenium through your normal package manager; replace legacy capability names and deprecated APIs; then run the full suite and fix failures one at a time. Selenium 4 uses the W3C WebDriver standard and removes the legacy JSON Wire Protocol. Code that was already W3C compliant in late Selenium 3 is expected to work, but capabilities and Actions are the areas most likely to need attention. See the official Selenium migration guide.

1. Inventory the suite before changing it

Record the language binding and exact Selenium version, the runtime version, browser versions, driver installation method, and whether tests run locally, in containers, or on a cloud grid. Note any custom capabilities, browser options, proxies, and Actions chains.

  • Identify every dependency declaration, lockfile, and CI image that pins Selenium or the runtime.
  • Confirm the Java runtime if you use Java. Selenium 4.13 was the final release with Java 8 support; the Selenium team advised upgrading to at least Java 11.
  • Save a baseline test result and representative logs so protocol, environment, and application failures are easier to distinguish.
  • Check the migration notes and release notes for the exact Selenium version you intend to adopt. The latest release identified in the research for this guide is 4.47, announced August 10, 2026; release details can change.

2. Update the dependency using your package manager

Choose a Selenium 4 version compatible with your runtime and project constraints. Update the dependency in the project’s normal package manager, then refresh its lockfile. Do not copy version pins from older migration examples: those illustrate where to declare the dependency, not which version is current.

Binding Where to update Check before selecting a version
Java Maven or Gradle dependency declaration Java runtime compatibility and framework constraints
Python Requirements file or project dependency manager Python version and pinned transitive dependencies
.NET NuGet package reference Target framework and package constraints
Ruby Gemfile Ruby runtime and lockfile
JavaScript npm or other project package manager Node.js runtime and lockfile

Use your package manager’s current registry metadata to choose a release. Resolve the dependency in a branch and commit the lockfile change with the migration so CI and developer machines use the same binding version.

3. Replace legacy protocol capabilities

Selenium 4 speaks W3C WebDriver. Standard capabilities use W3C names: replace version with browserVersion and platform with platformName. Standard names also include browserName, acceptInsecureCerts, pageLoadStrategy, proxy, timeouts, and unhandledPromptBehavior.

Capabilities specific to a browser vendor or remote grid are not standard capabilities. Put them in the vendor’s documented options object and use the required vendor prefix. For cloud-only values such as build and test name, follow that provider’s current schema rather than sending them as unprefixed top-level capabilities.

// Before: legacy capability keys
capabilities.setCapability("version", "latest");
capabilities.setCapability("platform", "Linux");

// After: W3C standard capability keys
capabilities.setCapability("browserVersion", "latest");
capabilities.setCapability("platformName", "Linux");

The snippet shows the key migration only; use the options and capability builder supported by your binding and remote provider. Avoid sending both the old and new names as a workaround: remove obsolete keys and validate the resulting session against the target browser or grid.

4. Update binding-specific APIs

Java

Timeout APIs now use java.time.Duration instead of a number paired with TimeUnit. Update implicit wait, script timeout, and page-load timeout calls. Selenium’s internal-use FindsBy interfaces were removed; use the public WebDriver locator methods such as findElement and findElements.

import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

WebDriver driver = /* create your driver */;
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().timeouts().scriptTimeout(Duration.ofMinutes(2));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(10));
driver.findElement(By.id("submit"));

In a real suite, retain the project’s normal driver lifecycle and choose timeout values based on the application’s needs. Do not add a long implicit wait to mask synchronization problems; explicit waits usually make the expected condition clearer.

Python

Replace the deprecated executable_path constructor argument with a Service object, or make the driver available on PATH and allow Selenium’s driver setup to locate it.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService

service = ChromeService(executable_path="/path/to/chromedriver")
driver = webdriver.Chrome(service=service)
try:
    driver.get("https://example.com")
    print(driver.title)
finally:
    driver.quit()

If the driver is already on PATH, the setup can be shorter:

from selenium import webdriver

driver = webdriver.Chrome()
try:
    driver.get("https://example.com")
    print(driver.title)
finally:
    driver.quit()

C#, Ruby, and JavaScript

Update the binding through its package manager, inspect deprecation output, and consult the migration and release notes for that binding. Do not assume a Java or Python API change maps directly to another language. In particular, inspect Actions usage and remote capability construction, then update calls based on the binding’s current public API.

5. Run the suite in controlled stages

  1. Compile or import the project after the dependency update. Fix removed symbols and signature changes before starting browsers.
  2. Run one local smoke test against each browser family you support. Confirm session creation, navigation, element lookup, waits, screenshots, and clean shutdown.
  3. Run tests against the remote grid or cloud provider with the migrated capability structure.
  4. Run the full suite in CI using the intended runtime and pinned dependency lockfile.
  5. Classify failures as startup/protocol, browser-driver compatibility, changed API behavior, timing/Actions behavior, or application/test flakiness. Fix the cause rather than loosening every timeout.

Actions sequences deserve focused review. W3C-compliant input behavior can expose assumptions that were hidden by older protocol handling. Recheck key and pointer sequences, pauses, element focus, and whether an element is still interactable when the action executes.

6. Troubleshooting common migration failures

Symptom Likely cause Fix
Session creation rejects a capability Legacy name, unprefixed vendor field, or malformed options object Use W3C names for standard fields; place provider-specific values in its documented prefixed options object.
Driver constructor reports an unknown argument Binding API changed, such as Python’s removed executable_path parameter Use the binding’s Service or current driver setup API and check its migration notes.
Java compilation fails on timeout calls Old numeric value plus TimeUnit signature Pass a Duration, and import java.time.Duration.
Java dependency or runtime fails to load Project runs Java 8 with a Selenium release after 4.13 Upgrade to at least Java 11 or choose a release compatible with the project runtime while planning that upgrade.
Browser starts locally but not on the grid Remote endpoint expects provider-specific capability nesting or a supported browser version Validate the grid’s current capability schema and supported browser/runtime combination.
Clicks or key input behave differently Actions sequence relies on legacy protocol behavior, stale focus, or timing assumptions Review the sequence step by step, ensure the target is ready and interactable, and use explicit synchronization where needed.
Tests hang during navigation or script execution Timeout values changed, page-load strategy differs, or the page never reaches the expected state Set appropriate Duration-based timeouts and wait for the application condition the test needs.
Only CI fails CI image uses a different runtime, browser, driver, dependency lock, or environment variable Compare versions and startup configuration with the local smoke test; pin the intended dependencies and browser environment.

7. Performance, reliability, and cost

Selenium 4 migration is not itself a performance optimization. Measure suite duration before and after using the same test selection and environment. Browser startup, grid queueing, application readiness, and test synchronization often affect elapsed time more than the binding upgrade. Keep waits specific, avoid unnecessary browser restarts, and use parallel execution only within the capacity and isolation limits of your grid.

For reliability, pin the Selenium dependency and runtime in CI, record browser and driver versions, and keep a small startup smoke test. When updating later, read the release notes for the selected version: Selenium 4.47’s notes include changes involving BiDi, .NET command options, Firefox CDP access, and Selenium Manager, so details can be binding-specific.

Budget for the engineering work of dependency updates, runtime upgrades, grid configuration changes, and intermittent failures during rollout. Selenium itself is the automation framework; infrastructure and browser execution costs depend on your environment or provider. The research provides no basis for a universal migration time or savings estimate.

8. When a screenshot API is useful alongside Selenium

Selenium is appropriate when a test must interact with a browser: click controls, enter data, or verify a workflow. If a task only needs a page image or PDF, a screenshot API can avoid maintaining browser setup for that capture. ScreenshotNeo provides a website screenshot API and MCP server, with options including full-page capture, element capture, device and viewport selection, custom waits, and PDF output. Its API also supports custom CSS and JavaScript, request blocking, headers, cookies, caching, and asynchronous jobs. See the ScreenshotNeo API documentation.

Or skip the browser setup

For a page capture, one GET request returns an image or PDF. Replace the target URL and API key with your values:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Use the response headers to distinguish outcomes: ScreenshotNeo reports the page verdict and billing status with X-Page-Verdict and X-Billed. Cookie banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000, and every feature is on every plan.

Sign up for ScreenshotNeo’s free 1,000 screenshots per month, with no card required.

FAQ

Will every Selenium 3 test work unchanged?

No guarantee applies to every suite. W3C-compliant code from the latest Selenium 3 is expected to work; review capabilities, Actions, and deprecated binding APIs.

Should I migrate every binding at once?

For a multi-language repository, migrate and validate each binding independently while keeping shared grid capability conventions consistent.

Do I need to use Selenium Manager?

Not necessarily. The migration guide’s Python example supports an explicit Service object, and a driver available on PATH is another setup. Follow your project’s driver-management approach and check release notes for changes.

Can ScreenshotNeo replace Selenium tests?

No. It is useful for captures and PDFs; browser interaction and workflow assertions remain Selenium tasks.