This is the multi-page printable view of this section. Click here to print.
Blog Posts - 2026
- Selenium 4.46 Released!
- Selenium 4.45 Released!
- Selenium 4.44 Released!
- .NET Assemblies Are Now Strongly Signed
- Selenium 4.43 Released!
- Selenium 4.42 Released!
- Selenium Grid Docker Images Are Now Mirrored to GHCR
- The two-week Flutter: Why Chrome’s New Cadence is a 'Non-Event' for Selenium Users
- Selenium Grid 4.41.0: What's New and Why It Matters
- Selenium 4.41 Released!
- Selenium 4.40 Released!
Selenium 4.46 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
SessionQueueintroduced 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
WebDriverWaitimport example in a docstring.
Ruby
- Generated the BiDi protocol layer from the shared binding-neutral schema.
- Added
ClientConfigfor HTTP client customization, and trimmed whitespace aroundNO_PROXYentries.
.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
SessionQueueintroduced 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_cddlscript 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
Vasiliy Mikhailov
Selenium Docs & Website
Ajith KV
Noritaka Kobayashi
Rahul Kant jha
Docker Selenium
Selenium Team Members
Thanks as well to all the team members who contributed to this release:
Augustin Gottlieb
Andrei Solntsev
David Burns
Boni García
Corey Goldberg
Swastik Baranwal
Diego Molina
Nikolay Borisenko
Puja Jagani
Simon Mavi Stewart
Titus Fortner
Viet Nguyen Duc
Stay tuned for updates by following SeleniumHQ on:
Happy automating!
Selenium 4.45 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 publishedselenium-devtools-latestartifact. - 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
SessionQueuesupport alongside the already-bundled RedisSessionMap. - [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
curbHTTP client andChromium Profileclasses, and split responsibilities betweenService,DriverFinder, andOptions. - [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
ElectronOptionsandElectronDriver. - Published a
selenium-devtools-latestartifact 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
curbHTTP client support and theChromium Profileclasses. - Separated concerns between
Service,DriverFinder, andOptions. - 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
SessionMapby default and added Redis-backedSessionQueuesupport. - 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
Docker Selenium
Nikita Sergeev
Selenium Team Members
Thanks as well to all the team members who contributed to this release:
Andrei Solntsev
David Burns
Boni García
Swastik Baranwal
Diego Molina
Sri Harsha
Navin Chandra
Nikolay Borisenko
Puja Jagani
Pallavi Sharma
Simon Mavi Stewart
Titus Fortner
Viet Nguyen Duc
Stay tuned for updates by following SeleniumHQ on:
Happy automating!
Selenium 4.44 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.mdfiles 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
UnhandledPromptBehavioras a string or map. - [Java] Continued JSpecify nullability work in the
remotepackage, deprecated nativeHttpClientmethods, and fixed aNoSuchElementExceptionregression for customBylocators. - [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
getAttributeandisDisplayedatoms from Closure to TypeScript.
📦 Notable Changes
Java
- Continued JSpecify nullability annotations in the
remotepackage. - Deprecated native methods on the
HttpClientinterface. - Fixed a regression causing
NoSuchElementExceptionfor customBylocators, and an NPE when a response status is null. - Removed the unused
ChromiumDriver.capabilitiesfield, and bumpedbyte-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
-vand--logflags. - Standardized
READMEfiles 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
UnhandledPromptBehavioraccept 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.mdfiles. - 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
Chandan
Devang
David Zbarsky
Selenium Docs & Website
Bansidhar kadiya
Vincent Adiasor
LetyPG
Srinivas Rao Tammireddy
Docker Selenium
Ilia Lazebnik
Selenium Team Members
Thanks as well to all the team members who contributed to this release:
Augustin Gottlieb
Andrei Solntsev
David Burns
Boni García
Corey Goldberg
Diego Molina
Sri Harsha
Nikolay Borisenko
Michael Render
Simon Mavi Stewart
Titus Fortner
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 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.WebDriverandSelenium.Supportare now strongly signed. Assembly names change:WebDriver→Selenium.WebDriver,WebDriver.Support→Selenium.Support.AssemblyVersionstays at4.0.0.0(major-only) — no binding redirects needed between4.xreleases.Selenium.WebDriver.StrongNamedandSelenium.Support.StrongNamedare retired.
Breaking Changes
If you reference the StrongNamed packages, migrate as follows:
- Replace
Selenium.WebDriver.StrongNamed→Selenium.WebDriver - Replace
Selenium.Support.StrongNamed→Selenium.Support - Update assembly references:
WebDriver.StrongNamed→Selenium.WebDriver,WebDriver.Support.StrongNamed→Selenium.Support
If you use the regular packages — the assemblies are now signed but the assembly names change (WebDriver.dll → Selenium.WebDriver.dll, WebDriver.Support.dll → Selenium.Support.dll), so update any explicit assembly references accordingly. The public API is unchanged.
Related Issues
- #12315 — Publish StrongNamed builds to NuGet feed
- #10845 — Revisiting strong-named assemblies for NuGet
- #14115 — Strong Name Key
Implementation: #17397
Selenium 4.43 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
browserNameleaked 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
browserNamewhen 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
Stay tuned for updates by following SeleniumHQ on:
Happy automating!
Selenium 4.42 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
speculationmodule, continued JSpecify nullability coverage (chromium, browsers, grid, devtools, docker packages), and fixed severalKeys/ScriptKeycorrectness issues. - [Grid] Hardened the WebSocket router: a transparent TCP tunnel bypass path, handling for dropped close frames and idle disconnects, and a new
NodeCommandInterceptorfor pluggable command interception via--ext. - [Python] Expanded BiDi test coverage and added type stubs for lazily imported classes.
- [JavaScript] Added a
Colorclass to the JavaScript library. - [Build] Bumped to Bazel 9.
📦 Notable Changes
Java
- Implemented the BiDi
speculationmodule. - Continued JSpecify nullability annotations across the
chrom*, browsers,grid.*,devtools, anddockerpackages. - Deduplicated Unicode PUA mappings in
Keys, madeOPTIONan alias ofALT, and deprecatedFN. - Fixed a regression that unnecessarily serialized binary streams in
RemoteWebDriver.builder(), and guarded against an NPE inPlatform.extractFromSysProperty. - Enhanced
ScriptKey.toString()and masked script content inUnpinnedScriptKey.
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_outputstreams.
Ruby
- Switched to portable Ruby.
- Fixed a linter error in the
./go authorsscript.
.NET
- Unified all BiDi event arguments to a consistent
*EventArgstype, 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
WebDriverdisposable 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
NodeCommandInterceptorfor 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
Jitendra Singh2
JAYA DILEEP
Selenium Docs & Website
Rushikesh Dehankar
Selenium Team Members
Thanks as well to all the team members who contributed to this release:
Andrei Solntsev
David Burns
Corey Goldberg
Diego Molina
Sri Harsha
Navin Chandra
Nikolay Borisenko
Alex Rodionov
Simon Mavi Stewart
Titus Fortner
Viet Nguyen Duc
Stay tuned for updates by following SeleniumHQ on:
Happy automating!
Selenium Grid Docker Images Are Now Mirrored to GHCR
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:
nightlytags 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
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 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] configsis present in configKubernetesSessionFactory.java— orchestrates Pod lifecycle via the official Kubernetes Java clientKubernetesOptions.java— configuration model for namespace, pod specs, resource limits, tolerations, node selectors, image pull secrets, and moreInheritedPodSpec.java— propagates the Node Pod’s own spec fields (service account, tolerations, affinity, PVCs) to browser Jobs automaticallyKubernetesFlags.java— CLI flags under the--kubernetes-*prefix, mirroring every option in the TOML[kubernetes]section- A new help text file
kubernetes.txtdocumenting all configuration options
New Docker images
Two new container images land in docker-selenium:
NodeKubernetes(Docker Hub) — the Dynamic Grid node that talks to the Kubernetes APIStandaloneKubernetes(Docker Hub) — a fully self-contained single-binary Standalone in Kubernetes mode
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:
- 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. - Abnormal close reason — the
session-closedevent is received but carries areasonother thanQUIT_COMMAND. Currently, that meansTIMEOUT(session exceeded the configured timeout) orNODE_REMOVED(the node was forcibly removed from the Grid). A cleandriver.quit()sendsQUIT_COMMANDand 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
IngressRouteand 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:
| Feature | PR(s) | Impact |
|---|---|---|
| Dynamic Grid for Kubernetes | selenium#17092, docker-selenium#3082 | On-demand browser Pods natively in K8s clusters |
| Session Event API | selenium#17015 | Client-to-grid event push, ZeroMQ sidecar consumption, all language bindings |
| Event-driven video recording | docker-selenium#3070, docker-selenium#3084 | Precise start/stop by default; failure-only upload with SE_UPLOAD_FAILURE_SESSION_ONLY |
| Traefik default ingress | docker-selenium#3083 | Ahead of Ingress NGINX EOL; better WebSocket support |
| Distributor reliability | selenium#17104, selenium#17106, selenium#17022, selenium#17109 | Thread exhaustion, WebSocket leaks, deadlock, retry fixed |
| Browser version matrix | docker-selenium#3085 | 167+ image variants across Chrome, Edge, Firefox; interactive Fluxbox browser menu |
| Structured logs | docker-selenium#3087 | SE_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)
| PR | Description |
|---|---|
| #17015 | Add session event API for server-side event bus integration |
| #17022 | Preventing potential deadlock in Distributor |
| #17060 | Revert default standalone config |
| #17072 | Dynamic Grid standalone support passing basic auth credential |
| #17092 | Support Dynamic Grid in Kubernetes cluster |
| #17097 | Restore stereotype capabilities merging in RelaySessionFactory |
| #17104 | Fix Distributor thread exhaustion in node health-check cycle |
| #17106 | Fix WebSocket connection counter leaks in ProxyNodeWebsockets |
| #17109 | Distributor retry session when RemoteNode executor shutting down |
Docker Selenium (4.41.0-20260222)
| PR | Description |
|---|---|
| #3070 | Video recorder/uploader listen on session events |
| #3082 | Images for Dynamic Grid in Kubernetes |
| #3083 | Replace Ingress NGINX with Traefik for default ingress controller |
| #3084 | Enable SE_VIDEO_EVENT_DRIVEN by default |
| #3085 | Add browser selection to Fluxbox menu and lightweight terminal |
| #3086 | Missing tolerations for monitoring exporter deployment |
| #3087 | Add config to disable plain logs while enabling structured logs |
| #3088 | Unified configs for Dynamic Grid Docker and Kubernetes |
| #3089 | Video event-driven service deal with standalone status endpoint has basic auth |
Selenium 4.41 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, expandedSetViewport), a newSpeculationmodule, 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_overrideemulation 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, andbidi.scriptpackages. - Implemented BiDi
emulation.setScreenSettingsOverride. - Added JSpecify annotations for
LoadableComponentandSlowLoadableComponent. - 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_overrideemulation 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__.pyand splitruffinto 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 expandedSetViewport/AddPreloadScript/MovePointerin the BiDi Emulation and Input modules. - Added a new BiDi
Speculationmodule and made commands/events fully immutable. - Made Selenium Manager and the driver service start/stop asynchronous (breaking changes), and added
CancellationTokensupport 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_DEBUGwarning 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
cookie
denay
Docker Selenium
Mårten Svantesson
Selenium Team Members
Thanks as well to all the team members who contributed to this release:
ian zhang
Andrei Solntsev
David Burns
Corey Goldberg
Swastik Baranwal
Diego Molina
Sri Harsha
Navin Chandra
Nikolay Borisenko
Michael Render
Pallavi Sharma
Titus Fortner
Viet Nguyen Duc
Stay tuned for updates by following SeleniumHQ on:
Happy automating!
Selenium 4.40 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
LocalWebDriverbase 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
LocalWebDriverbase class. - Removed deprecated
FirefoxBinaryandFTP proxysupport. - Improved linting and typing with
ruffandmypy. - 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
Selenium Docs & Website
ian zhang
Docker Selenium
AvivGuiser
Selenium Team Members
Thanks as well to all the team members who contributed to this release:
Augustin Gottlieb
Andrei Solntsev
David Burns
Boni García
Corey Goldberg
Diego Molina
Sri Harsha
Nikolay Borisenko
Alex Rodionov
Puja Jagani
Michael Render
Simon Benzer
Simon Mavi Stewart
Titus Fortner
Viet Nguyen Duc
Stay tuned for updates by following SeleniumHQ on:
Happy automating!




