Selenium 4.46 Released!

Today we’re happy to announce that Selenium 4.46 has been released!

We’re excited to announce the release of Selenium 4.46 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.46 Released

✨ Highlights

  • Security Selenium Manager (Rust) now guards against path traversal when extracting tar/pkg archives.
  • [Java] Hardened the JSON parser for RFC 8259 compliance (rejecting unescaped control characters, fixing an EOF sentinel collision, correcting comma handling), and marked all current BiDi classes as beta ahead of further changes.
  • BiDi Continued unifying the BiDi protocol layer across bindings around a shared, binding-neutral CDDL-derived schema — now adopted by JavaScript and Ruby.
  • [Java] BiDi creation moved to RemoteWebDriver, with BiDi now enabled for Safari options and event subscriptions tracked by subscription ID.
  • [Python] Safari, Safari Technology Preview, and WebView2 now route to their own dedicated handlers.
  • [Rust] Selenium Manager fixes for Firefox binary discovery on Linux and macOS .pkg/pbzx payload support.
  • [Grid] Fixed classpath packaging for the Redis-backed SessionQueue introduced in 4.45.

📦 Notable Changes

Java

  • Hardened the JSON parser: fixed an EOF sentinel collision with U+FFFF, rejected unescaped control characters, tightened the number lexer to RFC 8259, and corrected comma handling.
  • Marked all current BiDi classes as beta, moved BiDi creation to RemoteWebDriver, and enabled BiDi for Safari options.
  • Deprecated methods that surfaced the internal BiDi connection on the driver, and now track event subscriptions by subscription ID.
  • Fixed the Docker version comparator, and system proxy handling for Selenium Manager arguments.

Python

  • Routed Safari, Safari Technology Preview, and WebView2 to their own handlers.
  • Fixed the WebDriverWait import example in a docstring.

Ruby

  • Generated the BiDi protocol layer from the shared binding-neutral schema.
  • Added ClientConfig for HTTP client customization, and trimmed whitespace around NO_PROXY entries.

.NET

  • Made product info determination thread-safe.
  • Split event-stream-backed subscription and enumeration for BiDi, and continued test infrastructure cleanup.

Grid

  • Fixed classpath packaging for the Redis-backed SessionQueue introduced in 4.45.

Build & Infra

  • Added a design decision record (ADR) process and template for documenting significant changes.
  • Added a binding-neutral BiDi schema (JavaScript) and an update_cddl script to refresh pinned w3c/webref CDDL files.
  • Bumped Rust build tooling (rules_rs, @llvm) and the Node.js toolchain.

🐳 Docker Selenium

  • Docker: Add Redis external datastore support for SessionQueue configuration (#3160)
  • Docker: Fix video.sh busy-loop when se:recordVideo=false (#3165)
  • Docker: Env var SE_VIDEO_SESSION_SUBFOLDER to standardize recording in dynamic grid (#3156)
  • See all changes

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium

Viacheslav Dermichev

Viacheslav Dermichev

Vasiliy Mikhailov

Vasiliy Mikhailov

Selenium Docs & Website

Noritaka Kobayashi

Noritaka Kobayashi

Rahul Kant jha

Rahul Kant jha

Docker Selenium

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

Augustin Gottlieb

Augustin Gottlieb

Andrei Solntsev

Andrei Solntsev

David Burns

David Burns

Boni García

Boni García

Corey Goldberg

Corey Goldberg

Swastik Baranwal

Swastik Baranwal

Diego Molina

Diego Molina

Nikolay Borisenko

Nikolay Borisenko

Puja Jagani

Puja Jagani

Simon Mavi Stewart

Simon Mavi Stewart

Titus Fortner

Titus Fortner

Viet Nguyen Duc

Viet Nguyen Duc

Stay tuned for updates by following SeleniumHQ on:

Happy automating!

Selenium 4.45 Released!

Today we’re happy to announce that Selenium 4.45 has been released!

We’re excited to announce the release of Selenium 4.45 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.45 Released

✨ Highlights

  • [Java] New support for driving Electron apps via ElectronOptions/ElectronDriver, plus a published selenium-devtools-latest artifact.
  • Cross-binding Continued migration of shared “atoms” (isDisplayed, getAttribute, find-elements) from Closure to TypeScript, now adopted across JavaScript, Ruby, Java, and .NET.
  • [Grid] Hardened the WebSocket proxy (pre-handshake race, TCP backpressure, fast-path framing) and added Redis-backed SessionQueue support alongside the already-bundled Redis SessionMap.
  • [Python] New high-level BiDi network APIs for request/response/auth handling and extra headers, plus BiDi script module alignment with the cross-binding API design.
  • [Ruby] Deprecated the curb HTTP client and Chromium Profile classes, and split responsibilities between Service, DriverFinder, and Options.
  • [Rust] Selenium Manager now prunes cache entries older than 30 days and switched its TLS backend to ring.
  • [Build] Broad CI/release modernization: Bazel bumped to 9.1 and a rerun-safe release pipeline.

📦 Notable Changes

Java

  • Added support for driving Electron apps with ElectronOptions and ElectronDriver.
  • Published a selenium-devtools-latest artifact and removed deprecated logging classes.
  • Corrected deprecation annotations for JSON Wire error code APIs, and skip browser-restricted ports when picking a free port.
  • Adopted the shared TypeScript atoms for isShown/getAttribute.

Python

  • Added high-level BiDi network APIs: request, response, and authentication handlers, plus extra headers support.
  • Aligned the BiDi script module with the cross-binding API design, and close the BiDi websocket on quit().
  • Extracted actions, alert, and color into dedicated libraries/subpackages.
  • Marked Safari tests broken by SafariDriver 26.5 as expected failures.

Ruby

  • Deprecated curb HTTP client support and the Chromium Profile classes.
  • Separated concerns between Service, DriverFinder, and Options.
  • Adopted the shared TypeScript atoms, added Safari tests, and upgraded to Steep 2.0.

.NET

  • Added support for using any external BiDi transport, plus a fake BiDi transport for tests.
  • Expanded JSON data available on BiDi commands, events, and results, and added a download ID to download events.
  • Made user-facing BiDi collections immutable, and improved exception messages for malformed capability URLs.

Grid

  • Closed a pre-handshake race and applied TCP backpressure and fast-path framing in the WebSocket proxy.
  • Bundled a Redis-backed SessionMap by default and added Redis-backed SessionQueue support.
  • Added debug logging for file downloads.

Build & Infra

  • Bumped Bazel to 9.1 and made the release pipeline rerun-safe.
  • Set up trusted publishing from GitHub to npm, and generated release notes from the previous minor release tag.
  • Continued disk-space and Bazel cache monitoring improvements across CI jobs.

🐳 Docker Selenium

  • Docker: Add Redis external datastore support for Distributor configuration (#3137)
  • Docker: Video recording starts via session capability se:recordVideo (#3131)
  • K8s: Migrate subchart Redis to cloudpirates/redis from bitnami/redis (#3148)
  • See all changes

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium Docs & Website

Noritaka Kobayashi

Noritaka Kobayashi

Docker Selenium

Nikita Sergeev

Nikita Sergeev

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

Andrei Solntsev

Andrei Solntsev

David Burns

David Burns

Boni García

Boni García

Swastik Baranwal

Swastik Baranwal

Diego Molina

Diego Molina

Sri Harsha

Sri Harsha

Navin Chandra

Navin Chandra

Nikolay Borisenko

Nikolay Borisenko

Puja Jagani

Puja Jagani

Pallavi Sharma

Pallavi Sharma

Simon Mavi Stewart

Simon Mavi Stewart

Titus Fortner

Titus Fortner

Viet Nguyen Duc

Viet Nguyen Duc

Stay tuned for updates by following SeleniumHQ on:

Happy automating!

Selenium 4.44 Released!

Today we’re happy to announce that Selenium 4.44 has been released!

We’re excited to announce the release of Selenium 4.44 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.44 Released

✨ Highlights

  • [Build] Introduced a new Selenium CLI tool, plus an AI-assisted contribution policy and Rust commands to install skills.md/rules.md files for agent-based contributors.
  • [.NET] A large batch of BiDi alignment work (statically declared commands/events, additional event streaming) alongside several breaking changes: obsolete member removal and UnhandledPromptBehavior as a string or map.
  • [Java] Continued JSpecify nullability work in the remote package, deprecated native HttpClient methods, and fixed a NoSuchElementException regression for custom By locators.
  • [Grid] Added a Redis-backed Distributor as a built-in option and fixed latent WebSocket proxy bugs.
  • [Python] Now generates BiDi files instead of hand-curating them, and added a CDDL 2 Python generator.
  • [JavaScript] Converted the getAttribute and isDisplayed atoms from Closure to TypeScript.

📦 Notable Changes

Java

  • Continued JSpecify nullability annotations in the remote package.
  • Deprecated native methods on the HttpClient interface.
  • Fixed a regression causing NoSuchElementException for custom By locators, and an NPE when a response status is null.
  • Removed the unused ChromiumDriver.capabilities field, and bumped byte-buddy.

Python

  • Switched to generated BiDi files instead of hand-curated ones, and added a CDDL 2 Python generator.
  • Implemented high-level APIs for script execution and improved generated BiDi module docstrings.
  • Added an Edge service argument to inherit browser I/O streams.
  • Updated docs with a pytest example and general formatting fixes.

Ruby

  • Fixed a credential issue with private keys.
  • Fixed a conflict between Firefox’s -v and --log flags.
  • Standardized README files across bindings.

.NET

  • Statically declared BiDi commands and events, and added additional event streaming (breaking change).
  • Removed members planned for deprecation in 4.44, and made UnhandledPromptBehavior accept a string or map (breaking change).
  • Migrated tests to MTP and strongly signed assemblies.
  • Fixed network monitoring stop behavior and internal log truncation at error/warn levels.

Grid

  • Added a Redis-backed Distributor as built-in support.
  • Accept legacy session-closed event payloads.
  • Fixed latent bugs in the WebSocket proxy.

Build & Infra

  • Introduced the Selenium CLI tool and an AI-assisted contribution policy, including Rust commands to install skills.md/rules.md files.
  • Hardened the release pipeline: trusted publishing for Python, GitHub release drafts before publish, and parallelized documentation releases.
  • Cleaned up GHA runner disk space and fixed several release/prerelease workflow issues.

🐳 Docker Selenium

  • Docker: Retain recordings for failed sessions only from session capabilities (#3111)
  • chart(selenium-grid): add ServiceMonitor and PodMonitor support for Prometheus Operator (#3121)
  • Docker: Cleanup CfT and Chromium profiles (#3123)
  • See all changes

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium

Devang

Devang

David Zbarsky

David Zbarsky

Selenium Docs & Website

Bansidhar kadiya

Bansidhar kadiya

Vincent Adiasor

Vincent Adiasor

LetyPG

LetyPG

Srinivas Rao Tammireddy

Srinivas Rao Tammireddy

Docker Selenium

Ilia Lazebnik

Ilia Lazebnik

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

Augustin Gottlieb

Augustin Gottlieb

Andrei Solntsev

Andrei Solntsev

David Burns

David Burns

Boni García

Boni García

Corey Goldberg

Corey Goldberg

Diego Molina

Diego Molina

Sri Harsha

Sri Harsha

Nikolay Borisenko

Nikolay Borisenko

Michael Render

Michael Render

Simon Mavi Stewart

Simon Mavi Stewart

Titus Fortner

Titus Fortner

Viet Nguyen Duc

Viet Nguyen Duc

Stay tuned for updates by following SeleniumHQ on:

Happy automating!

.NET Assemblies Are Now Strongly Signed

Starting with Selenium 4.44, the main NuGet packages ship strongly signed assemblies. The separate StrongNamed packages are retired.

Starting with Selenium 4.44, the Selenium.WebDriver and Selenium.Support NuGet packages ship strongly signed assemblies. The separate Selenium.WebDriver.StrongNamed and Selenium.Support.StrongNamed packages are discontinued.

Why This Matters

Without strong naming on the official packages, any project that itself needed to be strongly signed could not reference Selenium from NuGet. Teams were forced to either forgo strong naming, bundle out-of-band downloads, or use the separate StrongNamed packages with a different assembly name — all of which caused friction, especially in enterprise environments.

What Changed

  • Selenium.WebDriver and Selenium.Support are now strongly signed. Assembly names change: WebDriverSelenium.WebDriver, WebDriver.SupportSelenium.Support.
  • AssemblyVersion stays at 4.0.0.0 (major-only) — no binding redirects needed between 4.x releases.
  • Selenium.WebDriver.StrongNamed and Selenium.Support.StrongNamed are retired.

Breaking Changes

If you reference the StrongNamed packages, migrate as follows:

  1. Replace Selenium.WebDriver.StrongNamedSelenium.WebDriver
  2. Replace Selenium.Support.StrongNamedSelenium.Support
  3. Update assembly references: WebDriver.StrongNamedSelenium.WebDriver, WebDriver.Support.StrongNamedSelenium.Support

If you use the regular packages — the assemblies are now signed but the assembly names change (WebDriver.dllSelenium.WebDriver.dll, WebDriver.Support.dllSelenium.Support.dll), so update any explicit assembly references accordingly. The public API is unchanged.

  • #12315 — Publish StrongNamed builds to NuGet feed
  • #10845 — Revisiting strong-named assemblies for NuGet
  • #14115 — Strong Name Key

Implementation: #17397

Selenium 4.43 Released!

Today we’re happy to announce that Selenium 4.43 has been released!

We’re excited to announce the release of Selenium 4.43 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.43 Released

This is a quick follow-up patch to 4.42, fixing a Grid regression.

✨ Highlights

  • [Grid] Fixed a regression where a mobile slot’s stereotype browserName leaked into merged session capabilities instead of being filtered out when an app-related capability was present.

📦 Notable Changes

Grid

  • Restored filtering so that merged session capabilities only include browserName when no app-related capability is present.

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

Diego Molina

Diego Molina

Stay tuned for updates by following SeleniumHQ on:

Happy automating!

Selenium 4.42 Released!

Today we’re happy to announce that Selenium 4.42 has been released!

We’re excited to announce the release of Selenium 4.42 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.42 Released

✨ Highlights

  • [.NET] A large BiDi consistency pass: unified event arguments, exposed BiDi functionality via interfaces, thread-safe event registration and processing, and commands/events that allocate close to nothing.
  • [Java] Implemented the BiDi speculation module, continued JSpecify nullability coverage (chromium, browsers, grid, devtools, docker packages), and fixed several Keys/ScriptKey correctness issues.
  • [Grid] Hardened the WebSocket router: a transparent TCP tunnel bypass path, handling for dropped close frames and idle disconnects, and a new NodeCommandInterceptor for pluggable command interception via --ext.
  • [Python] Expanded BiDi test coverage and added type stubs for lazily imported classes.
  • [JavaScript] Added a Color class to the JavaScript library.
  • [Build] Bumped to Bazel 9.

📦 Notable Changes

Java

  • Implemented the BiDi speculation module.
  • Continued JSpecify nullability annotations across the chrom*, browsers, grid.*, devtools, and docker packages.
  • Deduplicated Unicode PUA mappings in Keys, made OPTION an alias of ALT, and deprecated FN.
  • Fixed a regression that unnecessarily serialized binary streams in RemoteWebDriver.builder(), and guarded against an NPE in Platform.extractFromSysProperty.
  • Enhanced ScriptKey.toString() and masked script content in UnpinnedScriptKey.

Python

  • Expanded BiDi test coverage.
  • Added type stubs for lazily imported classes and modules.
  • Added a module for importing the latest DevTools version, and stopped closing externally provided log_output streams.

Ruby

  • Switched to portable Ruby.
  • Fixed a linter error in the ./go authors script.

.NET

  • Unified all BiDi event arguments to a consistent *EventArgs type, and exposed BiDi functionality via interfaces.
  • Made event registration and command/event processing thread-safe, with close to zero allocation per command/event.
  • Properly handled the WebSocket close handshake, and made any WebDriver disposable asynchronously.
  • Bumped the C# language version to 14.0.

Grid

  • Added a transparent TCP tunnel bypass path for the WebSocket data path in the Router.
  • Handled dropped close frames, idle disconnects, and high-latency proxying in the Router WebSocket.
  • Added NodeCommandInterceptor for pluggable command interception via --ext.
  • Fixed VNC capabilities not being propagated for sessions without a browserName.

Build & Infra

  • Upgraded to Bazel 9.
  • Fixed several CI reliability issues (RBE tests, lint/format CI, skipped failing Firefox Beta tests).

🐳 Docker Selenium

  • Docker: Mirror images to GitHub Container Registry (#3098)
  • Docker: Enable SE_VIDEO_EVENT_DRIVEN by default (#3084)
  • Unified configs for Dynamic Grid Docker and Kubernetes (#3088)
  • See all changes

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium

Selenium Docs & Website

Rushikesh Dehankar

Rushikesh Dehankar

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

Andrei Solntsev

Andrei Solntsev

David Burns

David Burns

Corey Goldberg

Corey Goldberg

Diego Molina

Diego Molina

Sri Harsha

Sri Harsha

Navin Chandra

Navin Chandra

Nikolay Borisenko

Nikolay Borisenko

Alex Rodionov

Alex Rodionov

Simon Mavi Stewart

Simon Mavi Stewart

Titus Fortner

Titus Fortner

Viet Nguyen Duc

Viet Nguyen Duc

Stay tuned for updates by following SeleniumHQ on:

Happy automating!

Selenium Grid Docker Images Are Now Mirrored to GHCR

Official Selenium Grid Docker images are now mirrored to GitHub Container Registry, giving teams an official alternative to Docker Hub.

The request started with a simple question in issue #2939: can we publish Selenium Grid Docker images to GitHub Container Registry as well as Docker Hub?

The answer is now, Yes.

Official Selenium Grid Docker images are now available from GitHub Container Registry (GHCR) under:

ghcr.io/seleniumhq

This is a mirror, not a replacement. Docker Hub remains available, and existing users do not need to change anything. But if your environment prefers GHCR, blocks Docker Hub, or simply wants another official source for the same images, you now have one.

Why this matters

The original issue highlighted a problem many teams have run into over the last year: relying on a single public registry creates unnecessary friction.

Issue #2939, opened on August 27, 2025, started with Docker Hub rate limits but quickly exposed a broader need. For some teams, Docker Hub is inconvenient. For others, it is restricted entirely by policy. The discussion also surfaced concrete use cases around GitHub Actions workflows, especially from forks, enterprise environments that disallow Docker Hub, and local development without Docker Hub authentication.

Publishing to GHCR improves that situation in a few practical ways:

  • It gives users an official alternative registry for the same Selenium Grid images.
  • It reduces dependency on a single distribution channel.
  • It makes adoption easier for teams that already standardize on GitHub-hosted packages.
  • It improves resilience during registry outages, service limits, or access restrictions.

The result is a practical improvement to how Selenium Grid images are distributed: one image set, one familiar naming scheme, and more than one official place to pull from.

What is available

The GHCR namespace for Selenium Grid images is:

ghcr.io/seleniumhq

That namespace is important because the original feature request used example paths under ghcr.io/selenium/..., while the final published location is ghcr.io/seleniumhq/....

From the rollout tracked in issue #2939 and the current repository state:

  • nightly tags are available on GHCR for all images.
  • Release images are available on GHCR, with the first full release called out in the issue as 4.42.0-20260303.
  • Recent CI updates in the repository also extend GHCR mirroring for Dev/Beta browser images.

You can browse the published packages here:

How to pull from GHCR

If you already use Selenium Grid images from Docker Hub, switching to GHCR is mostly a matter of replacing the registry prefix.

For example:

docker pull ghcr.io/seleniumhq/standalone-chrome:latest

docker run -d -p 4444:4444 \
  ghcr.io/seleniumhq/standalone-chrome:latest

If you want a pinned release instead of latest:

docker pull ghcr.io/seleniumhq/standalone-firefox:4.42.0-20260303

If you are testing against the bleeding edge:

docker pull ghcr.io/seleniumhq/node-chrome:nightly

The naming model stays familiar. The main change is the registry hostname and namespace.

What is not changing

This rollout is intentionally additive.

  • Docker Hub images are still part of the Selenium Grid distribution story.
  • Existing commands that use selenium/<image>:<tag> continue to work.
  • Users can adopt GHCR at their own pace.

That matters because the goal here is flexibility, not churn. Teams that are happy with Docker Hub can stay where they are. Teams that need GHCR can move immediately without waiting for custom mirrors or maintaining internal rebuilds of Selenium Grid images.

Thank you

Thanks to everyone who raised the need, added concrete use cases, and helped push the rollout forward. Community feedback in issue #2939 helped move this from a feature request into delivered infrastructure.

If you are already using the new mirror, or plan to switch CI workloads over to GHCR, feedback on image coverage and pull experience will help guide the next refinements.

Happy testing!

The two-week Flutter: Why Chrome’s New Cadence is a 'Non-Event' for Selenium Users

This blog post discusses the move of Chrome going to do a 14 day release cycle and how it’s mostly a non-event for Selenium users.

If you’ve been following the Chromium blog, you’ll have seen the news: Chrome is moving to a two-week release cycle. Starting in September 2026, the pace of the web is effectively doubling. In the old days of manual browser driver management, a faster release cadence meant more frequent SessionNotCreatedException errors and more time spent hunting for the right chromedriver binary to match your updated browser.

But I’m here to tell you: Don’t panic. If you are using a modern version of Selenium (v4.11 or newer), this change is effectively a “non-event.” Thanks to Selenium Manager, the days of manually synchronizing your browser and driver are over.

The Problem: The Versioning Treadmill

Historically, automation engineers were stuck in a reactive loop. Chrome would auto-update in the background, your tests would fail because the driver on your PATH was stale, and you’d spend your morning manually downloading a .zip file.

As the release cycle moves to every two weeks, the “manual” cost of maintenance becomes unsustainable. You shouldn’t be a “Binary Manager”; you should be a Test Engineer.

The Solution: Selenium Manager & Chrome for Testing (CfT)

A few years ago, the Selenium project introduced Selenium Manager, a tool bundled with every Selenium release. It works in tandem with Google’s Chrome for Testing (CfT)—a dedicated flavor of Chrome specifically for automation that doesn’t “stealth update” and has its own versioned endpoints.

When your code starts a session, Selenium Manager silently handles the heavy lifting:

Detection: It checks which version of Chrome is currently on your machine.

Resolution: It discovers the matching ChromeDriver version via the CfT endpoints.

Acquisition: If Chrome isn’t found (or you need a specific version), it downloads the correct Chrome for Testing binary and the driver for you.

Caching: It stores these in ~/.cache/selenium so you aren’t re-downloading them every time.

What this looks like in practice

You don’t need to change your code to handle the two-week updates. Follow our docs on how to use it, including if you need to pin your tests to a specific version of Chrome.

The Bottom Line

The web is moving faster, and browser vendors are shipping features and security patches at an incredible rate. Our goal at Selenium is to ensure that the infrastructure of your tests remains invisible.

The move to a two-week cycle is a testament to how far web engineering has come. With Selenium Manager, you can enjoy the benefits of a modern browser without the headache of manual maintenance.

Keep your tests green and your drivers automated.

Selenium Grid 4.41.0: What's New and Why It Matters

We are excited to ship Selenium Grid 4.41.0 — and this release brings something meaningful for you

We are excited to ship Selenium Grid 4.41.0 🎉 — and this might be one of impactful releases in recent memory. Whether you are running Grid in a bare-metal lab, a Docker Compose stack, or a sprawling Kubernetes cluster, this release brings something meaningful for you. From a brand-new Dynamic Grid for Kubernetes, a powerful Session Event API, to smarter video recording and a rock-solid Distributor, let’s dig in.


1. Dynamic Grid, Now Native on Kubernetes

The headline feature of 4.41.0 is unambiguous: Dynamic Grid now runs natively inside Kubernetes clusters (selenium#17092, docker-selenium#3082).

Background

Dynamic Grid is the architecture where Selenium Grid provisions browser containers on demand per test session — no pre-warmed browser pods sitting idle, no slot over-allocation, no manual scaling. Before 4.41.0, this capability was only available when the Grid node ran alongside a Docker daemon, making it a Docker-first feature.

For Kubernetes users, scaling the Grid has long meant one of two paths: static node deployments with fixed slot pools, or integrating KEDA with the Selenium Grid Scaler. The KEDA approach has matured considerably over the years and works well — but it comes with a certain complexity. Users must reason about and maintain two separate systems simultaneously: KEDA’s ScaledObject configuration and the Grid’s own node and session settings. Getting them to agree on scale-up thresholds, cooldown periods, and session slot counts requires ongoing attention, and any Grid upgrade potentially touches both sides.

Dynamic Grid for Kubernetes takes a different approach: scaling is an intrinsic behaviour of the Grid itself, not an external concern. The Grid provisions exactly one browser Pod per session request and deletes it immediately on close. There is no separate scaler to configure or keep in sync.

With 4.41.0, we introduce KubernetesSessionFactory — a full Kubernetes-aware session factory that creates ephemeral browser Pods on the fly per session request, and tears them down immediately when the session closes.

What’s new in the core (Java)

Underpinning this entire feature is a new Service Provider Interface (SPI): NodeSessionFactoryProvider. This interface lets anyone ship a custom session factory as an external jar, loaded at runtime via --ext, without touching selenium-server.jar:

public interface NodeSessionFactoryProvider {
  /** Return true when this provider's config section is present. */
  boolean isEnabled(Config config);

  /** Return the capability→factory mappings this provider contributes. */
  Map<Capabilities, Collection<SessionFactory>> loadFactories(
      Config config, Tracer tracer, HttpClient.Factory clientFactory);
}

LocalNodeFactory discovers all implementations on the classpath via ServiceLoader at startup — if isEnabled() returns true, the factories are merged into the node’s slot pool. This keeps the core jar lean and opens the door for community extensions targeting any container runtime, cloud provider, or custom provisioning system.

The Kubernetes support in this release is the first implementation of this SPI. A new kubernetes sub-package under grid/node brings:

  • KubernetesSessionFactoryProvider.java — the @AutoService-annotated SPI entry point; isEnabled() activates when [kubernetes] configs is present in config
  • KubernetesSessionFactory.java — orchestrates Pod lifecycle via the official Kubernetes Java client
  • KubernetesOptions.java — configuration model for namespace, pod specs, resource limits, tolerations, node selectors, image pull secrets, and more
  • InheritedPodSpec.java — propagates the Node Pod’s own spec fields (service account, tolerations, affinity, PVCs) to browser Jobs automatically
  • KubernetesFlags.java — CLI flags under the --kubernetes-* prefix, mirroring every option in the TOML [kubernetes] section
  • A new help text file kubernetes.txt documenting all configuration options

New Docker images

Two new container images land in docker-selenium:

Along with them, reference Kubernetes manifests are available at kubernetes/DynamicGrid/. These are intentionally simplex — designed for local practice and getting started quickly, not as production blueprints:

kubernetes/DynamicGrid/
├── BaseConfig/
│   ├── configmap.yaml   # Grid config, browser stereotypes
│   ├── pvc.yaml         # Shared video/asset storage
│   └── rbac.yaml        # ServiceAccount + ClusterRole to create/delete Pods
├── Hub_Node/
│   ├── hub-deployment.yaml
│   ├── hub-svc.yaml
│   └── node-kubernetes-deployment.yaml
└── Standalone/
    └── standalone-kubernetes.yaml

Minimal Hub+Node setup

Browser stereotypes and Dynamic Grid tuning live in a TOML config file, delivered to the Node Pod via a ConfigMap:

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: selenium-kubernetes-config
data:
  kubernetes.toml: |
    [kubernetes]
    configs = [
        "selenium/standalone-chrome:4.41.0-20260222", '{"browserName": "chrome", "platformName": "linux"}',
        "selenium/standalone-firefox:4.41.0-20260222", '{"browserName": "firefox", "platformName": "linux"}',
        "selenium/standalone-edge:4.41.0-20260222", '{"browserName": "MicrosoftEdge", "platformName": "linux"}'
    ]

The configs array pairs each browser image with a capability stereotype JSON string. The Node uses these to match incoming session requests against the right image, spin up the Pod, and report available slots to the Hub.

The Node deployment then mounts that ConfigMap as a file and points the Grid node at it:

# node-kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: selenium-node-kubernetes
spec:
  replicas: 1
  template:
    spec:
      serviceAccountName: selenium-node        # needs Pod create/delete RBAC
      terminationGracePeriodSeconds: 300
      containers:
        - name: selenium-node-kubernetes
          image: selenium/node-kubernetes:4.41.0-20260222
          ports:
            - containerPort: 5555
          env:
            - name: SE_EVENT_BUS_HOST
              value: "selenium-hub"
            - name: SE_NODE_SESSION_TIMEOUT
              value: "600"
            - name: SE_DYNAMIC_OVERRIDE_MAX_SESSIONS
              value: "true"
            - name: SE_DYNAMIC_MAX_SESSIONS
              value: "10"
          volumeMounts:
            - name: selenium-config
              mountPath: /opt/selenium/kubernetes.toml  # TOML config path
              subPath: kubernetes.toml
              readOnly: true
            - name: session-assets                      # shared PVC for video/logs
              mountPath: /opt/selenium/assets
      volumes:
        - name: selenium-config
          configMap:
            name: selenium-kubernetes-config            # references the ConfigMap above
        - name: session-assets
          persistentVolumeClaim:
            claimName: selenium-assets

Event bus connectivity (host), and node config (session timeout) are passed as environment variables. Browser stereotypes live entirely in the TOML file inside the ConfigMap, keeping them independently updatable without redeploying the Node.

The RBAC manifest grants the Node the minimal permissions needed — create, delete, and get on pods and pods/log in the configured namespace. No cluster-wide permissions needed.

Scaling strategy with InheritedPodSpec

InheritedPodSpec is what makes multi-Node deployments particularly powerful. When the Dynamic Grid Node runs inside Kubernetes, it inspects its own Pod spec and automatically propagates matching fields to every browser Job it creates — tolerations, affinity rules, node selectors, resource requests and limits, image pull secrets, service account, DNS config, security context, and PVC mounts at the assets path.

This means the browser Pods land on exactly the same node pool, zone, or hardware tier as the Dynamic Node that spawned them — with no extra configuration required in the TOML or ConfigMap.

The practical implication: you can deploy multiple Dynamic Grid Nodes, each pinned to a different cluster segment via their own nodeSelector or affinity, and all registered to the same Hub. The Hub distributes session requests across them, and each Node provisions browser Pods that inherit its own placement constraints.

                         ┌─────────────────────────────────-┐
                         │             Hub                  │
                         └──────┬──────────────┬───────────-┘
                                │              │
               ┌────────────────▼──┐      ┌───-▼────────────────┐
               │ NodeKubernetes    │      │ NodeKubernetes      │
               │ nodeSelector:     │      │ nodeSelector:       │
               │   zone=us-west    │      │   zone=us-east      │
               └────────┬──────────┘      └────────-┬───────────┘
                        │ inherits spec             │ inherits spec
              ┌─────────▼──────────┐    ┌──────────-▼──────────┐
              │  browser Pod       │    │  browser Pod         │
              │  (us-west, same    │    │  (us-east, same      │
              │   tolerations...)  │    │   tolerations...)    │
              └────────────────────┘    └─────────────────────-┘

Teams can keep multiple Dynamic Nodes on standby — lightweight processes consuming minimal resources — and let the browser Pods scale horizontally on demand within each segment. This approach fits naturally into cluster autoscaler workflows: idle Nodes hold their spot in the cluster; browser Jobs drive the actual compute scaling.

This is a genuinely exciting milestone. Teams running large Kubernetes fleets can now enjoy true on-demand browser provisioning without a Docker socket sidecar. The browser Pod starts when the session starts, and disappears the moment it ends. Resources are consumed only when tests are actually running.


2. Session Event API: A Bridge to the Outside World

The second headline feature is the Session Event API (selenium#17015).

The problem it solves

Many teams have built tooling around Selenium Grid — dashboards, alerting systems, test analytics platforms, video archival pipelines. Before this release, there was no stable way for test code to signal custom events to the Grid infrastructure layer. You couldn’t tell the video recorder “this test just failed, add a marker” or tell a log collector “the interesting part is about to start” without building your own out-of-band channel.

4.41.0 introduces a proper, documented client-to-grid event push endpoint that lets test code fire named events with arbitrary payloads directly through the Grid node, onto the internal event bus where sidecar services can react.

How it works

The new endpoint lives on the Node, scoped to an active session:

POST /session/{sessionId}/se/event

The request body carries an eventType (a namespaced string) and an optional payload map:

{
  "eventType": "test:failed",
  "payload": {
    "testName": "LoginTest",
    "error": "Element not found",
    "screenshot": true
  }
}

The Node validates the event, enriches it with nodeId, nodeUri, and timestamp, then fires a SessionEvent onto the ZeroMQ event bus. Sidecar services — like the video recorder — subscribe to the session-event topic on the same bus and react accordingly. The full wire format of the enriched SessionEventData published on the bus:

{
  "sessionId": "abc123",
  "eventType": "test:failed",
  "nodeId": "node-uuid",
  "nodeUri": "http://node:5555",
  "timestamp": "2026-02-24T10:00:00Z",
  "payload": { "testName": "LoginTest", "error": "Element not found" }
}

eventType follows a namespace:action convention (e.g., test:started, test:failed, log:collect, marker:add). The value must start with a letter and contain only alphanumerics, colons, dots, underscores, or hyphens — anything else is rejected at the Node.

Multi-language client support

Firing events is surfaced directly on the driver instance in all five official language bindings (required binding version 4.41.0+ also). No manual HTTP wiring needed — the driver knows the session ID and Grid URL:

# Python
driver.fire_session_event("test:started")
driver.fire_session_event("test:failed", {"testName": "LoginTest", "error": "Element not found"})
// Java
driver.fireSessionEvent("test:started");
driver.fireSessionEvent("test:failed", Map.of("testName", "LoginTest", "error", "Element not found"));
// JavaScript
await driver.fireSessionEvent('test:started');
await driver.fireSessionEvent('test:failed', { testName: 'LoginTest', error: 'Element not found' });
# Ruby
driver.fire_session_event("test:started")
driver.fire_session_event("test:failed", { testName: "LoginTest", error: "Element not found" })
// .NET
driver.FireSessionEvent("test:started");
driver.FireSessionEvent("test:failed", new Dictionary<string, object>
{
    { "testName", "LoginTest" },
    { "error", "Element not found" }
});

Under the hood each binding issues a POST /session/{sessionId}/se/event. You can also call it directly via cURL for scripting or debugging:

curl -X POST http://localhost:4444/session/${SESSION_ID}/se/event \
  -H 'Content-Type: application/json' \
  -d '{"eventType": "test:failed", "payload": {"testName": "LoginTest"}}'

Sidecar consumption via ZeroMQ

The receiving side subscribes directly to the Grid’s ZeroMQ event bus — the same bus used by SessionCreatedEvent and SessionClosedEvent. The video_service.py already does this for the three built-in topics:

# video_service.py (simplified)
for event in ["session-created", "session-closed", "session-event"]:
    self.subscriber.setsockopt_string(zmq.SUBSCRIBE, event)

This is not limited to Hub+Node setups. Standalone can expose the same publish and subscribe ports so any sidecar can tap into the event bus alongside it:

java -jar selenium-server.jar standalone \
  --bind-bus true \
  --events-implementation "org.openqa.selenium.events.zeromq.ZeroMqEventBus" \
  --publish-events "tcp://*:4442" \
  --subscribe-events "tcp://*:4443"

With --bind-bus true, Standalone starts its own ZeroMQ event bus and exposes it on the configured ports — the same ports your sidecar would connect to for subscriptions. This makes it straightforward to build any sidecar that reacts to lifecycle events and custom test signals through a single subscription, regardless of whether you are running a full Grid cluster or a single Standalone container.

This event-driven foundation also opens the door for docker-selenium to continue expanding its built-in tooling. Two natural directions on the roadmap: recording headless browser sessions that today have no display to capture, and recording trace views by connecting to the session’s WebSocket CDP endpoint at the right moment in the session lifecycle — both of which become straightforward sidecars once you know precisely when a session starts and which CDP endpoint it exposes.


3. Event-Driven Video Recording — Now the Default

Video recording in docker-selenium has grown significantly smarter, and in 4.41.0 it becomes the default behavior (docker-selenium#3070, docker-selenium#3084).

Before: timer-based recording

Previously, the Video container used a timer-based heuristic to decide when to start and stop recording. For Grid Hub/Node setups this was manageable, but for Standalone mode it was fragile — the recorder had no reliable way to know when a session started or ended. It would sometimes start too early or cut off the last frames.

After: event-driven recording

With the new Video/video_service.py, the video container subscribes to the Grid’s ZeroMQ event bus directly. When a session-created event arrives, recording starts. When session-closed fires, recording stops and the uploader kicks in.

[Selenium Node] ──ZeroMQ──► [video_service.py] ──start/stop──► [FFmpeg recorder]
  (event bus)   session-created / session-closed / session-event  └──upload──► [S3/GCS/Azure]

SE_VIDEO_EVENT_DRIVEN=true is now the default. The old polling-based mode is still available for backward compatibility by setting SE_VIDEO_EVENT_DRIVEN=false.

Standalone mode gets special handling (docker-selenium#3089): when Standalone is configured with basic auth, the event-driven service now correctly passes credentials when polling the /status endpoint used to verify readiness before subscribing to events.

Upload only failed sessions with SE_UPLOAD_FAILURE_SESSION_ONLY

This is where the event-driven architecture pays off in a particularly practical way for storage costs.

Set SE_UPLOAD_FAILURE_SESSION_ONLY=true and the uploader will skip cloud upload for sessions that passed — only failed or abnormally-closed sessions get their video uploaded. In a large test suite where the vast majority of tests pass, this can cut video storage significantly.

A session is treated as failed under two conditions:

  1. Custom failure event fired — your test framework calls driver.fire_session_event("test:failed", ...) during the session. The sidecar matches the event type against a configurable list of failure patterns.
  2. Abnormal close reason — the session-closed event is received but carries a reason other than QUIT_COMMAND. Currently, that means TIMEOUT (session exceeded the configured timeout) or NODE_REMOVED (the node was forcibly removed from the Grid). A clean driver.quit() sends QUIT_COMMAND and is never treated as a failure.

The failure event type matching is controlled by SE_UPLOAD_FAILURE_SESSION_EVENTS (default: :failed,:failure). The match is a substring check on the event type, so the default catches test:failed, suite:failed, step:failure, etc. You can extend the list for your own conventions:

# Upload only videos where test:failed, test:error, or test:aborted were fired
SE_UPLOAD_FAILURE_SESSION_ONLY=true
SE_UPLOAD_FAILURE_SESSION_EVENTS=":failed,:failure,:error,:aborted"

The full flow from test code to selective upload:

driver.fire_session_event("test:failed", {"testName": "LoginTest"})
        │
        ▼ POST /session/{id}/se/event  (Node HTTP endpoint)
        │
        ▼ ZeroMQ  topic: session-event
        │
        ▼ video_service.py marks session.is_failed = True
        │
        ▼ on session-closed: upload because is_failed == True
                             (skip upload if is_failed == False and upload_failure_only == True)

This makes the Session Event API and event-driven video a cohesive feature pair: the API is not just a notification mechanism — it directly drives infrastructure decisions like whether a video is worth keeping.

What this means for you

Recordings are now precisely bounded to the session lifecycle. No more partial recordings, no blank frames at the start, no abrupt cuts at the end. For teams uploading videos to cloud storage as test artifacts, the uploader now reliably fires exactly once per session, immediately on close — and with SE_UPLOAD_FAILURE_SESSION_ONLY, only the sessions that matter.


4. Traefik Replaces Ingress NGINX as the Default Helm Ingress Controller

If you deploy Selenium Grid via Helm, take note: Traefik is now the default ingress controller (docker-selenium#3083).

Why the switch?

The timing is deliberate. Per the official Kubernetes blog announcement in November 2025, the Ingress NGINX Helm chart will no longer be maintained after March 2026. With that EOL approaching, now is the right moment to move rather than wait for users to be stranded on an unmaintained dependency.

Traefik is a strong candidate to fill that gap, and it brings several advantages specifically for Selenium Grid’s traffic patterns:

  • Native WebSocket support — critical for BiDi (WebDriver BiDi) and CDP proxying through Grid, with no special annotation gymnastics
  • Built-in middleware — Traefik’s IngressRoute and middleware CRDs allow expressing Grid’s routing requirements (path rewrites, basic auth, sticky sessions) more cleanly
  • Better observability — native Prometheus metrics endpoint out of the box

The PR includes a comprehensive MIGRATION_INGRESS_NGINX_TO_TRAEFIK.md guide covering all common migration scenarios.

Migration in brief

# values.yaml — before
ingress:
  enabled: true
  className: "nginx"
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"

# values.yaml — after
ingress:
  enabled: true
  className: "traefik"
  # Traefik handles WS and timeouts natively via ServersTransport

A new traefik-servers-transport.yaml template is included in the chart, pre-configured with appropriate dial and response timeouts for Selenium Grid workloads.


5. Distributor Reliability — Serious Under-the-Hood Work

Three significant Distributor fixes ship in 4.41.0, addressing real concurrency issues that could manifest under load.

Fix 1: Thread exhaustion in health-check cycle (selenium#17104)

The LocalDistributor runs periodic health checks against all registered nodes. Under high node counts, a subtle bug caused the health-check executor thread pool to accumulate tasks faster than they were being consumed, eventually exhausting threads and causing new session requests to stall.

The fix refactors LocalNodeRegistry.java to decouple the health-check scheduling from the main distribution path, with new tests specifically exercising concurrent health-check scenarios.

Fix 2: WebSocket connection counter leaks (selenium#17106)

ProxyNodeWebsockets tracks active WebSocket connections per node to respect maxSessions. A race condition in the connection bookkeeping could cause the counter to drift upward, making slots appear occupied when they were free. Over time this would cause nodes to appear artificially full.

The fix tightens the lifecycle management with try-finally guards around counter decrements.

Fix 3: Retry session on executor shutdown (selenium#17109)

When a RemoteNode’s thread executor enters a shutdown state (e.g., during a graceful drain), session creation requests could be silently dropped instead of being returned to the queue for redistribution. The Distributor now detects RejectedExecutionException from a shutting-down executor and transparently retries the session on another available node.

Deadlock prevention (selenium#17022)

LocalGridModel contained a lock inversion risk between its internal state lock and the event bus listener lock. Under specific timing conditions this could deadlock the Distributor entirely. The fix restructures lock acquisition order with a dedicated test (LocalGridModelDeadlockTest.java) that explicitly reproduces the hazard.

These four fixes together make the Distributor measurably more robust at scale. If you have ever seen mysterious session queue stalls or nodes appearing full when they shouldn’t be, upgrading to 4.41.0 is strongly recommended.


6. Docker Selenium as a Browser Version Matrix

Before getting into the remaining improvements, this is a good moment to highlight something that often goes unnoticed by newcomers: docker-selenium is not just an infrastructure tool — it is also a versioned browser library.

A standalone container behaves like a lightweight virtual machine. You pull a specific image tag, start it, and you have an isolated browser environment with a precise browser version and matching driver, ready to connect over WebDriver. No installation, no system-level conflicts, no flakiness from browser auto-updates.

# Reproduce a bug against Chrome 118 specifically
docker run -d -p 4444:4444 -p 5900:5900 selenium/standalone-chrome:118.0-20260222

# Run your test
pytest tests/regression/login_test.py --grid http://localhost:4444

# You also can use a VNC client and connect to host port 5900 to interact like a virtual machine

With 4.41.0, docker-selenium ships 167+ image variants across four browser families — Chrome, Chrome for Testing, Edge, and Firefox — spanning versions 95 through 148. The full matrix for every release is tracked in the CHANGELOG/ directory, where each browser version gets its own file listing the exact image tags, browser version, and driver version bundled together.

This makes docker-selenium the go-to tool whenever you need to:

  • Reproduce a user-reported bug on a specific browser version without touching your local machine
  • Run a cross-version compatibility sweep in CI across a range of browser versions in parallel
  • Pin a version in a test environment and guarantee it never changes until you deliberately upgrade

Interactive debugging with Fluxbox browser menu (docker-selenium#3085)

Complementing the above, the Fluxbox desktop inside every node and standalone image now has a right-click menu with browser launchers and a lightweight terminal (xterm). Connect to the container via VNC or noVNC, right-click the desktop, and launch the browser directly — no command line needed. When you are debugging a test failure visually against a specific image version, this removes the last friction from the interactive workflow.


7. More Improvements Worth Knowing About

Unified Dynamic Grid configuration (Docker & Kubernetes) (docker-selenium#3088)

Configuration environment variables are now shared across NodeDocker, StandaloneDocker, NodeKubernetes, and StandaloneKubernetes images. Variables like SE_NODE_GRID_URL, SE_NODE_MAX_SESSIONS, and video-related settings behave identically regardless of whether the Dynamic Grid backend is Docker or Kubernetes.

Basic auth support in Dynamic Grid Standalone (selenium#17072)

When Grid Standalone is secured with HTTP basic auth, DockerSessionFactory now correctly forwards credentials when communicating with the node’s own status endpoint during session setup. A small but important fix that unblocks secured Dynamic Grid deployments.

Restore stereotype capability merging in RelaySessionFactory (selenium#17097)

A regression introduced in a prior release caused RelaySessionFactory to ignore stereotype capabilities during session creation, which broke mobile relay sessions that relied on custom capabilities from the stereotype being merged into the session request.

Kubernetes: structured logs support (docker-selenium#3087)

Structured logging (global.seleniumGrid.structuredLogs) already existed, but plain-text logs were always emitted alongside it with no way to suppress them. This PR adds SE_PLAIN_LOGS (--plain-logs, default true) as an independent toggle. Both modes can run simultaneously — to get pure JSON output for a log aggregation pipeline (Loki, Elasticsearch), enable structured logs and turn plain logs off:

# Helm values.yaml
global:
  seleniumGrid:
    structuredLogs: true
    plainLogs: false

Or via environment variable on any component directly:

SE_PLAIN_LOGS=false

Kubernetes: tolerations fix for monitoring exporter (docker-selenium#3086)

The Helm chart’s monitoring exporter deployment was missing tolerations, which caused it to fail scheduling on tainted nodes (a common pattern in production clusters with dedicated node pools). This is now fixed.


Upgrading

Docker Compose

# Pull latest images
docker pull selenium/hub:4.41.0-20260222
docker pull selenium/node-chrome:4.41.0-20260222
docker pull selenium/video:ffmpeg-8.1-20260222

Or update your compose file:

services:
  chrome:
    image: selenium/node-chrome:4.41.0-20260222

Helm

helm repo update
helm upgrade selenium-grid docker-selenium/selenium-grid \
  --version 0.52.0 \
  --reuse-values

Helm users: Review the Ingress NGINX to Traefik migration guide before upgrading if you have custom ingress configuration.

Standalone JAR

# Standard standalone
java -jar selenium-server.jar standalone

The Kubernetes Dynamic Grid module ships as a separate extension jar that is loaded at runtime via --ext. The simplest way to fetch it is with Coursier:

# Node with Dynamic Grid on Kubernetes (image mode)
java -jar selenium-server.jar \
  --ext $(coursier fetch -p org.seleniumhq.selenium:selenium-node-kubernetes:latest.release) \
  node \
  -K selenium/standalone-chrome:latest '{"browserName": "chrome"}' \
  -K selenium/standalone-firefox:latest '{"browserName": "firefox"}' \
  -K selenium/standalone-edge:latest '{"browserName": "MicrosoftEdge"}'

For complex Job definitions, store the full Job YAML in a ConfigMap and reference it with the configmap: prefix:

java -jar selenium-server.jar \
  --ext $(coursier fetch -p org.seleniumhq.selenium:selenium-node-kubernetes:latest.release) \
  node \
  -K configmap:my-chrome-template '{"browserName": "chrome"}'

Noted: The ConfigMap should be deployed to cluster and must contain a key named template whose value is a valid K8s Job YAML. The Job must have a container named browser with an image. Cross-namespace references use namespace/configmap-name.

Or pass a TOML config file (equivalent to the ConfigMap approach above):

java -jar selenium-server.jar \
  --ext $(coursier fetch -p org.seleniumhq.selenium:selenium-node-kubernetes:latest.release) \
  node \
  --config /opt/selenium/kubernetes.toml

Where kubernetes.toml contains the [kubernetes] section with configs, resource requests/limits, and any other tuning — matching exactly the ConfigMap content shown in the deployment section above.


Summary

Selenium Grid 4.41.0 is a release with real depth. The major themes:

FeaturePR(s)Impact
Dynamic Grid for Kubernetesselenium#17092, docker-selenium#3082On-demand browser Pods natively in K8s clusters
Session Event APIselenium#17015Client-to-grid event push, ZeroMQ sidecar consumption, all language bindings
Event-driven video recordingdocker-selenium#3070, docker-selenium#3084Precise start/stop by default; failure-only upload with SE_UPLOAD_FAILURE_SESSION_ONLY
Traefik default ingressdocker-selenium#3083Ahead of Ingress NGINX EOL; better WebSocket support
Distributor reliabilityselenium#17104, selenium#17106, selenium#17022, selenium#17109Thread exhaustion, WebSocket leaks, deadlock, retry fixed
Browser version matrixdocker-selenium#3085167+ image variants across Chrome, Edge, Firefox; interactive Fluxbox browser menu
Structured logsdocker-selenium#3087SE_PLAIN_LOGS toggle for JSON-only log pipelines

We are proud of this release and excited about what it unlocks — particularly the Kubernetes Dynamic Grid, which has been one of the most features for advanced users. The Session Event API is equally exciting as a foundation: we expect to see community-built integrations emerge quickly.

As always, feedback and bug reports are welcome on selenium/issues and docker-selenium/issues.

Happy testing!


References

Selenium Core (selenium-4.41.0)

PRDescription
#17015Add session event API for server-side event bus integration
#17022Preventing potential deadlock in Distributor
#17060Revert default standalone config
#17072Dynamic Grid standalone support passing basic auth credential
#17092Support Dynamic Grid in Kubernetes cluster
#17097Restore stereotype capabilities merging in RelaySessionFactory
#17104Fix Distributor thread exhaustion in node health-check cycle
#17106Fix WebSocket connection counter leaks in ProxyNodeWebsockets
#17109Distributor retry session when RemoteNode executor shutting down

Docker Selenium (4.41.0-20260222)

PRDescription
#3070Video recorder/uploader listen on session events
#3082Images for Dynamic Grid in Kubernetes
#3083Replace Ingress NGINX with Traefik for default ingress controller
#3084Enable SE_VIDEO_EVENT_DRIVEN by default
#3085Add browser selection to Fluxbox menu and lightweight terminal
#3086Missing tolerations for monitoring exporter deployment
#3087Add config to disable plain logs while enabling structured logs
#3088Unified configs for Dynamic Grid Docker and Kubernetes
#3089Video event-driven service deal with standalone status endpoint has basic auth

Selenium 4.41 Released!

Today we’re happy to announce that Selenium 4.41 has been released!

We’re excited to announce the release of Selenium 4.41 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.41 Released

✨ Highlights

  • [DotNet] Major BiDi expansion in the Emulation module (SetTouchOverride, SetNetworkConditions, expanded SetViewport), a new Speculation module, and async command dispatch throughout — including breaking changes that make Selenium Manager and the driver service start/stop asynchronously.
  • [Java] Continued JSpecify nullability coverage across all BiDi packages, plus a JSON parsing fix for numbers with an exponent.
  • [Grid] Multiple Dynamic Grid fixes: a Distributor deadlock, WebSocket connection counter leaks, thread exhaustion in health checks, and new Kubernetes cluster and basic-auth support.
  • [Python] New BiDi set_screen_settings_override emulation command, Edge BiDi tests enabled, and a large Bazel/lint modernization pass.
  • [Ruby] Debug logging on by default, generated atoms no longer stored in the repo, and dependency/lint updates.
  • [Build] A sweeping overhaul of the release and CI pipeline, and the retirement of the legacy Rake/CrazyFun build system.

📦 Notable Changes

Java

  • Expanded JSpecify nullability annotations across the bidi.browser, bidi.browsingcontext, bidi.emulation, bidi.module, bidi.log, bidi.network, and bidi.script packages.
  • Implemented BiDi emulation.setScreenSettingsOverride.
  • Added JSpecify annotations for LoadableComponent and SlowLoadableComponent.
  • Fixed JSON parsing of numbers with an exponent.
  • Improved the error message when the Grid dies, and fixed a secure/non-secure test failure.

Python

  • Added BiDi set_screen_settings_override emulation command and enabled Edge BiDi tests.
  • Modularized the Bazel build with per-module targets and integrated mypy type checking.
  • Switched to lazy imports in webdriver/__init__.py and split ruff into dedicated format/check targets.
  • Removed type stub packages from runtime dependencies and bumped dev dependencies to fix a vulnerability.

Ruby

  • Driver logs now output by default when debug is enabled.
  • Removed stored atoms in favor of generating them at build time.
  • Updated lint configuration, fixed rubocop offenses, and added missing unit tests.

.NET

  • Added SetTouchOverride, SetNetworkConditions, and expanded SetViewport/AddPreloadScript/MovePointer in the BiDi Emulation and Input modules.
  • Added a new BiDi Speculation module and made commands/events fully immutable.
  • Made Selenium Manager and the driver service start/stop asynchronous (breaking changes), and added CancellationToken support for async commands and event registration.
  • Fixed several logging issues: truncated internal log messages, streamed Selenium Manager output, and corrected devtools inline docs generation.

Grid

  • Fixed a potential deadlock in the Distributor and WebSocket connection counter leaks in ProxyNodeWebsockets.
  • Added a session event API for server-side event bus integration.
  • Added Kubernetes cluster support and basic-auth credential passing for Dynamic Grid standalone.
  • Fixed thread exhaustion in the node health-check cycle and session retries when a RemoteNode executor is shutting down.

Build & Infra

  • Continued the Bazel migration: per-language patch releases, consolidated nightly and pre-release workflows, and file-level test target indexing.
  • Removed the legacy Rake/CrazyFun build system entirely.
  • Added linting support for .NET and unified SE_DEBUG warning behavior across Python, Java, and JavaScript.

🐳 Docker Selenium

  • Docker: Video recorder/uploader now listen on session events (#3070)
  • Docker: Fixed spurious warning messages when basic auth isn’t configured (#3071)
  • K8s: Updated KEDA to 2.19.0 in the Helm chart (#3068)
  • See all changes

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium

Selenium Docs & Website

Docker Selenium

Mårten Svantesson

Mårten Svantesson

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

ian zhang

ian zhang

Andrei Solntsev

Andrei Solntsev

David Burns

David Burns

Corey Goldberg

Corey Goldberg

Swastik Baranwal

Swastik Baranwal

Diego Molina

Diego Molina

Sri Harsha

Sri Harsha

Navin Chandra

Navin Chandra

Nikolay Borisenko

Nikolay Borisenko

Michael Render

Michael Render

Pallavi Sharma

Pallavi Sharma

Titus Fortner

Titus Fortner

Viet Nguyen Duc

Viet Nguyen Duc

Stay tuned for updates by following SeleniumHQ on:

Happy automating!

Selenium 4.40 Released!

Today we’re happy to announce that Selenium 4.40 has been released!

We’re excited to announce the release of Selenium 4.40 for JavaScript, Ruby, Python, .NET, Java and the Grid! 🎉

Links to all assets can be found on our downloads page.


Selenium 4.40 Released

✨ Highlights

  • Chrome DevTools support is now: v144, v143, and v142.
  • [Java] Expanded BiDi support: setScreenOrientationOverride, setNetworkConditions, and command return type changes.
  • [Python] Introduced LocalWebDriver base class, improved docstring formatting and typing, removed deprecated code.
  • [Ruby] Better test synchronization, clearer BiDi API boundaries, and internal improvements.
  • [DotNet] BiDi improvements in Input, Emulation, and buffer pooling logic.
  • [Grid] Improved dynamic Grid container handling, better concurrent session handling, and race condition fixes.
  • [Build] Bazel-based documentation and release automation improvements across all bindings.

📦 Notable Changes

Java

  • Added support for new BiDi emulation and network commands.
  • Deprecated unused logging and Guava collections.
  • Improved test infrastructure and flaky test fixes.
  • Improved Grid file upload, JSON parsing, and error handling.
  • Automatically save screenshots and page sources on failure.

Python

  • Added LocalWebDriver base class.
  • Removed deprecated FirefoxBinary and FTP proxy support.
  • Improved linting and typing with ruff and mypy.
  • Better grid test integration and SE_DEBUG logging.

Ruby

  • Improved error handling and synchronization for socket interactions.
  • Marked BiDi internals as private.
  • Fixed various flaky test issues.

.NET

  • Support for SetScreenSettingsOverrideAsync, FileDialogOpened, and input module exposure.
  • Improved memory usage and buffer reuse in BiDi communication.
  • Removed obsolete or unstable APIs.

Grid

  • Smarter container naming and label handling in Dynamic Grid.
  • Race condition and concurrent session handling improvements.

Build & Infra

  • All bindings now support rerun on test failures.
  • Improved CI speed, clarity, and isolation.
  • Build/test infra modernized with Bazel rules and doc generation.

🐳 Docker Selenium

  • Docker: update video_graphQLQuery shell script to python (#3002)
  • Update ubuntu:noble Docker digest to cd1dba6 (#3063)
  • Update list new environment variables (#3062)
  • See all changes

We thank all our contributors for their incredible efforts in making Selenium better with every release. ❤️

For a detailed look at all changes, check out the release notes.


Contributors

Special shout-out to everyone who helped the Selenium Team get this release out!

Selenium

Michal Zyndul

Michal Zyndul

Selenium Docs & Website

ian zhang

ian zhang

Docker Selenium

AvivGuiser

AvivGuiser

Selenium Team Members

Thanks as well to all the team members who contributed to this release:

Augustin Gottlieb

Augustin Gottlieb

Andrei Solntsev

Andrei Solntsev

David Burns

David Burns

Boni García

Boni García

Corey Goldberg

Corey Goldberg

Diego Molina

Diego Molina

Sri Harsha

Sri Harsha

Nikolay Borisenko

Nikolay Borisenko

Alex Rodionov

Alex Rodionov

Puja Jagani

Puja Jagani

Michael Render

Michael Render

Simon Benzer

Simon Benzer

Simon Mavi Stewart

Simon Mavi Stewart

Titus Fortner

Titus Fortner

Viet Nguyen Duc

Viet Nguyen Duc

Stay tuned for updates by following SeleniumHQ on:

Happy automating!