From e1d5e0336dd536774c6ac50f3d8c3bba0ca49a71 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 05:16:45 +0000 Subject: [PATCH 1/7] test(remote): cover LocalFileDetector upload and managed download retrieval Add a remote file upload test using LocalFileDetector and extend the download test to actually retrieve the managed download to the client. Both exercise the file-operation forwarding path from SeleniumHQ/selenium#17914, where the Node forwards upload/downloadFile to the session whenever the browser does not share the Node filesystem. These tests run in the shared Selenium suite, so they cover Dynamic Grid on Docker and Kubernetes and Relay, where the browser runs in a separate container/Pod/endpoint. Static nodes and standalone keep working since LocalFileDetector transfer is a no-op against the local filesystem. The upload test is skipped for the Android emulator relay, where HTML file upload via LocalFileDetector does not apply. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- tests/SeleniumTests/__init__.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py index 46a2d4016..4756851fb 100644 --- a/tests/SeleniumTests/__init__.py +++ b/tests/SeleniumTests/__init__.py @@ -1,6 +1,8 @@ import concurrent.futures import os import random +import shutil +import tempfile import time import traceback import unittest @@ -11,6 +13,7 @@ from selenium.webdriver.edge.options import Options as EdgeOptions from selenium.webdriver.firefox.options import Options as FirefoxOptions from selenium.webdriver.remote.client_config import ClientConfig +from selenium.webdriver.remote.file_detector import LocalFileDetector from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait @@ -117,6 +120,35 @@ def test_play_video(self): paused = video.get_property('paused') self.assertFalse(paused) + def test_upload_file(self): + driver = self.driver + # A local file must be transferred to the machine running the browser before the browser + # can select it. When the browser does not share the Node filesystem (Dynamic Grid on + # Docker/Kubernetes and Relay) the Node forwards the upload to the session so the file lands + # where sendKeys runs. Use LocalFileDetector so the client-side file is transferred remotely. + # See SeleniumHQ/selenium#17914. + if TEST_NODE_RELAY == 'Android': + self.skipTest("HTML file upload via LocalFileDetector is not applicable to the Android emulator relay") + driver.file_detector = LocalFileDetector() + upload_dir = tempfile.mkdtemp() + file_name = 'selenium-upload.txt' + file_path = os.path.join(upload_dir, file_name) + try: + with open(file_path, 'w') as upload_file: + upload_file.write('docker-selenium remote upload test') + driver.get(f'http://{TEST_SITE}/upload') + wait = WebDriverWait(driver, WEB_DRIVER_WAIT_TIMEOUT) + file_input = wait.until(EC.presence_of_element_located((By.ID, 'file-upload'))) + file_input.send_keys(file_path) + driver.find_element(By.ID, 'file-submit').click() + uploaded_files = wait.until(EC.visibility_of_element_located((By.ID, 'uploaded-files'))) + self.assertTrue( + file_name in uploaded_files.text, + f"Uploaded file '{file_name}' not reported by the server, got '{uploaded_files.text}'", + ) + finally: + shutil.rmtree(upload_dir, ignore_errors=True) + def test_download_file(self): driver = self.driver driver.get(f'http://{TEST_SITE}/download') @@ -132,6 +164,19 @@ def test_download_file(self): lambda d: len(d.get_downloadable_files()) > 0 and str(d.get_downloadable_files()[0]).endswith(file_name) ) self.assertTrue(str(driver.get_downloadable_files()[0]).endswith(file_name)) + # Retrieve the managed download to the client. When the browser does not share the Node + # filesystem (Dynamic Grid on Docker/Kubernetes and Relay) the Node forwards downloadFile to + # the session, so the file is fetched from where the browser stored it. Asserting the + # retrieved file exists and is non-empty exercises that forwarding path. + # See SeleniumHQ/selenium#17914. + download_dir = tempfile.mkdtemp() + try: + driver.download_file(file_name, download_dir) + downloaded_file = os.path.join(download_dir, file_name) + self.assertTrue(os.path.isfile(downloaded_file), f"Downloaded file not found at {downloaded_file}") + self.assertGreater(os.path.getsize(downloaded_file), 0, "Downloaded file is empty") + finally: + shutil.rmtree(download_dir, ignore_errors=True) def tearDown(self): if TEST_CUSTOM_SPECIFIC_NAME: From d2c2fd68f11b53c26b74e2c917873f97d9092782 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 06:04:39 +0000 Subject: [PATCH 2/7] test(relay): verify relay session file forwarding via standalone browser Enable managed downloads on the desktop relay path (NodeFirefox relaying into the standalone browser) so the upload and download tests actually exercise the relay session file-operation forwarding from SeleniumHQ/selenium#17914. Previously the relay test disabled managed downloads, so test_download_file returned early and the relay download path was never verified. Now the relay target standalone runs with managed downloads enabled and the client requests downloads, so get_downloadable_files/downloadFile are forwarded through the relay node to the standalone browser, and LocalFileDetector uploads are forwarded the same way. This confirms the relay session reports a remote filesystem. Kept off for the Android emulator relay, where managed downloads and HTML file upload via LocalFileDetector do not apply. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- Makefile | 6 ++++-- tests/docker-compose-v3-test-node-relay.yml | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 30afd61cb..cdc63bf8d 100644 --- a/Makefile +++ b/Makefile @@ -1202,7 +1202,8 @@ test_node_relay: hub node_base standalone_firefox echo BASE_VERSION=$(BASE_VERSION) >> .env ; \ if [ $$node = "Android" ] ; then \ echo BROWSER=firefox >> .env \ - && echo BROWSER_NAME=firefox >> .env ; \ + && echo BROWSER_NAME=firefox >> .env \ + && echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=false >> .env ; \ fi ; \ if [ $$node = "NodeChrome" ] ; then \ echo BROWSER=chrome >> .env \ @@ -1219,7 +1220,8 @@ test_node_relay: hub node_base standalone_firefox fi ; \ if [ $$node = "NodeFirefox" ] ; then \ echo BROWSER=firefox >> .env \ - && echo BROWSER_NAME=firefox >> .env ; \ + && echo BROWSER_NAME=firefox >> .env \ + && echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), true) >> .env ; \ fi ; \ export $$(cat .env | xargs) ; \ envsubst < relay_config.toml > ./videos/relay_config.toml ; \ diff --git a/tests/docker-compose-v3-test-node-relay.yml b/tests/docker-compose-v3-test-node-relay.yml index 3b695f8e1..a7b6b0fb1 100644 --- a/tests/docker-compose-v3-test-node-relay.yml +++ b/tests/docker-compose-v3-test-node-relay.yml @@ -16,8 +16,9 @@ services: image: ${NAMESPACE}/standalone-${BROWSER}:${TAG} shm_size: 2gb environment: - - SE_OPTS=--enable-cdp true + - SE_OPTS=--enable-cdp true --enable-managed-downloads ${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} - SE_NODE_ENABLE_CDP=true + - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} selenium-hub: image: ${NAMESPACE}/hub:${TAG} @@ -46,7 +47,7 @@ services: - RUN_IN_DOCKER_COMPOSE=true - SELENIUM_GRID_HOST=selenium-hub - BINDING_VERSION=${BINDING_VERSION} - - SELENIUM_ENABLE_MANAGED_DOWNLOADS=false + - SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} - TEST_NODE_RELAY=${TEST_NODE_RELAY} - ANDROID_PLATFORM_API=${ANDROID_PLATFORM_API} - TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST} From 8bc72795b2cfa332b81719b5c48c729e3d848c1b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 06:14:44 +0000 Subject: [PATCH 3/7] test(dynamic-grid): enable managed downloads so remote download is verified Turn on managed downloads for the Dynamic Grid deployments that spawn the browser away from the Node, so the upload/download suite verifies the remote file forwarding from SeleniumHQ/selenium#17914 instead of skipping it. - Docker Dynamic Grid: default SELENIUM_ENABLE_MANAGED_DOWNLOADS to true in the test_node_docker target. It previously defaulted to false and only checked the legacy shared download volume; now the managed download API is retrieved through the DockerSession, exercising the forwarding path. - Kubernetes Dynamic Grid: enable managed downloads on the standalone-kubernetes and node-kubernetes deployments and default the test flag to true. The Node forwards downloadFile to the browser Job Pod, so managed downloads are now retrievable; the outdated comment saying they are not is updated. Enabling it on the Node mirrors the node-docker setup, where the dynamic-grid Node handles retrieval for the browsers it spawns without extra per-browser configuration. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- Makefile | 2 +- .../DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml | 2 ++ .../DynamicGrid/Standalone/standalone-kubernetes.yaml | 2 ++ tests/k8s/make/dynamic_grid_test.sh | 8 +++++--- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index cdc63bf8d..b2a220ca6 100644 --- a/Makefile +++ b/Makefile @@ -1268,7 +1268,7 @@ test_node_docker: hub standalone_docker standalone_chrome standalone_firefox sta echo TEST_PARALLEL_HARDENING=$(or $(TEST_PARALLEL_HARDENING), "false") >> .env ; \ echo LOG_LEVEL=$(or $(LOG_LEVEL), "INFO") >> .env ; \ echo REQUEST_TIMEOUT=$(or $(REQUEST_TIMEOUT), 300) >> .env ; \ - echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), "false") >> .env ; \ + echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), "true") >> .env ; \ echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 0) >> .env ; \ echo RECORD_STANDALONE=$(or $(RECORD_STANDALONE), "true") >> .env ; \ echo SE_UPLOAD_RETAIN_LOCAL_FILE=$(or $(SE_UPLOAD_RETAIN_LOCAL_FILE), "false") >> .env ; \ diff --git a/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml b/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml index 25fda268f..6daf3f47c 100644 --- a/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml +++ b/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml @@ -40,6 +40,8 @@ spec: value: "10" - name: SE_RECORD_VIDEO value: "true" + - name: SE_NODE_ENABLE_MANAGED_DOWNLOADS + value: "true" resources: requests: memory: "512Mi" diff --git a/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml b/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml index abf27e066..66bd36fa0 100644 --- a/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml +++ b/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml @@ -40,6 +40,8 @@ spec: value: "10" - name: SE_RECORD_VIDEO value: "true" + - name: SE_NODE_ENABLE_MANAGED_DOWNLOADS + value: "true" resources: requests: memory: "512Mi" diff --git a/tests/k8s/make/dynamic_grid_test.sh b/tests/k8s/make/dynamic_grid_test.sh index 77dc00418..303eca6af 100755 --- a/tests/k8s/make/dynamic_grid_test.sh +++ b/tests/k8s/make/dynamic_grid_test.sh @@ -237,9 +237,11 @@ export SELENIUM_GRID_PORT=${GRID_LOCAL_PORT} export SELENIUM_GRID_USERNAME=${GRID_USERNAME} export SELENIUM_GRID_PASSWORD=${GRID_PASSWORD} export SELENIUM_GRID_TEST_HEADLESS=${SELENIUM_GRID_TEST_HEADLESS:-"false"} -# The browser runs in its own Job Pod and downloads into it, while the Node serves the -# downloadable files from a Pod local directory, so managed downloads are not retrievable here -export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"false"} +# The browser runs in its own Job Pod and downloads into it. The Node forwards the managed +# download commands to the browser session instead of serving them from its own Pod local +# directory (SeleniumHQ/selenium#17914), so the files are retrievable from where the browser +# stored them. Enable managed downloads so the remote download verification exercises that path. +export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"true"} export TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST:-"0"} export BINDING_VERSION=${BINDING_VERSION} export BASE_VERSION=${BASE_VERSION} From 25e111f79e2364713f2752a64a38e01c2c9a0d2e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 13:23:23 +0000 Subject: [PATCH 4/7] fix(tests): gate remote upload/download to Selenium >= 4.48.0 deployments CI failed on the Relay and Kubernetes Dynamic Grid lanes after enabling remote downloads: those sessions only forward file upload/download to the browser with SeleniumHQ/selenium#17914 (Selenium 4.48.0). On the current release base, enabling managed downloads there makes get_downloadable_files time out and the LocalFileDetector upload land where the browser cannot see it. Gate the remote verification behind TEST_UPLOAD_DOWNLOAD_REMOTE (default true) and turn it off for the Relay and Kubernetes harnesses until the Grid base includes the fix. Docker Dynamic Grid and standalone keep verifying it, since DockerSession forwards on every Selenium version. Also revert the managed downloads enablement for the Relay standalone target and the Kubernetes Node deployments so those lanes return to their known-good state. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- Makefile | 6 ++---- .../Hub_Node/node-kubernetes-deployment.yaml | 2 -- .../DynamicGrid/Standalone/standalone-kubernetes.yaml | 2 -- tests/SeleniumTests/__init__.py | 11 +++++++++++ tests/docker-compose-v3-test-node-relay.yml | 3 +++ tests/k8s/make/dynamic_grid_test.sh | 11 ++++++----- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index b2a220ca6..61c2590e7 100644 --- a/Makefile +++ b/Makefile @@ -1202,8 +1202,7 @@ test_node_relay: hub node_base standalone_firefox echo BASE_VERSION=$(BASE_VERSION) >> .env ; \ if [ $$node = "Android" ] ; then \ echo BROWSER=firefox >> .env \ - && echo BROWSER_NAME=firefox >> .env \ - && echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=false >> .env ; \ + && echo BROWSER_NAME=firefox >> .env ; \ fi ; \ if [ $$node = "NodeChrome" ] ; then \ echo BROWSER=chrome >> .env \ @@ -1220,8 +1219,7 @@ test_node_relay: hub node_base standalone_firefox fi ; \ if [ $$node = "NodeFirefox" ] ; then \ echo BROWSER=firefox >> .env \ - && echo BROWSER_NAME=firefox >> .env \ - && echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), true) >> .env ; \ + && echo BROWSER_NAME=firefox >> .env ; \ fi ; \ export $$(cat .env | xargs) ; \ envsubst < relay_config.toml > ./videos/relay_config.toml ; \ diff --git a/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml b/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml index 6daf3f47c..25fda268f 100644 --- a/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml +++ b/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml @@ -40,8 +40,6 @@ spec: value: "10" - name: SE_RECORD_VIDEO value: "true" - - name: SE_NODE_ENABLE_MANAGED_DOWNLOADS - value: "true" resources: requests: memory: "512Mi" diff --git a/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml b/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml index 66bd36fa0..abf27e066 100644 --- a/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml +++ b/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml @@ -40,8 +40,6 @@ spec: value: "10" - name: SE_RECORD_VIDEO value: "true" - - name: SE_NODE_ENABLE_MANAGED_DOWNLOADS - value: "true" resources: requests: memory: "512Mi" diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py index 4756851fb..67d549e97 100644 --- a/tests/SeleniumTests/__init__.py +++ b/tests/SeleniumTests/__init__.py @@ -30,6 +30,11 @@ TEST_PARALLEL_COUNT = int(os.environ.get('TEST_PARALLEL_COUNT', 5)) TEST_DELAY_AFTER_TEST = int(os.environ.get('TEST_DELAY_AFTER_TEST', 0)) TEST_NODE_RELAY = os.environ.get('TEST_NODE_RELAY', 'false') +# Remote file upload (LocalFileDetector) and managed-download retrieval only work when the Node +# forwards the file operations to the browser session. That forwarding is unconditional for Docker +# Dynamic Grid, but for Relay and Kubernetes sessions it requires SeleniumHQ/selenium#17914 +# (Selenium >= 4.48.0). Those harnesses keep this off until the Grid base includes the fix. +TEST_UPLOAD_DOWNLOAD_REMOTE = os.environ.get('TEST_UPLOAD_DOWNLOAD_REMOTE', 'true').lower() == 'true' TEST_ANDROID_PLATFORM_API = os.environ.get('ANDROID_PLATFORM_API') TEST_PLATFORMS = os.environ.get('TEST_PLATFORMS', 'linux/amd64') TEST_FIREFOX_INSTALL_LANG_PACKAGE = os.environ.get('TEST_FIREFOX_INSTALL_LANG_PACKAGE', 'false').lower() == 'true' @@ -127,6 +132,10 @@ def test_upload_file(self): # Docker/Kubernetes and Relay) the Node forwards the upload to the session so the file lands # where sendKeys runs. Use LocalFileDetector so the client-side file is transferred remotely. # See SeleniumHQ/selenium#17914. + if not TEST_UPLOAD_DOWNLOAD_REMOTE: + self.skipTest( + "Remote file upload/download forwarding is disabled for this deployment (requires Selenium >= 4.48.0)" + ) if TEST_NODE_RELAY == 'Android': self.skipTest("HTML file upload via LocalFileDetector is not applicable to the Android emulator relay") driver.file_detector = LocalFileDetector() @@ -169,6 +178,8 @@ def test_download_file(self): # the session, so the file is fetched from where the browser stored it. Asserting the # retrieved file exists and is non-empty exercises that forwarding path. # See SeleniumHQ/selenium#17914. + if not TEST_UPLOAD_DOWNLOAD_REMOTE: + return download_dir = tempfile.mkdtemp() try: driver.download_file(file_name, download_dir) diff --git a/tests/docker-compose-v3-test-node-relay.yml b/tests/docker-compose-v3-test-node-relay.yml index a7b6b0fb1..900ce198b 100644 --- a/tests/docker-compose-v3-test-node-relay.yml +++ b/tests/docker-compose-v3-test-node-relay.yml @@ -19,6 +19,8 @@ services: - SE_OPTS=--enable-cdp true --enable-managed-downloads ${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} - SE_NODE_ENABLE_CDP=true - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} + # Managed downloads over Relay needs SeleniumHQ/selenium#17914 (Selenium >= 4.48.0); it stays + # off by default and is exercised once the Grid base includes the fix. selenium-hub: image: ${NAMESPACE}/hub:${TAG} @@ -48,6 +50,7 @@ services: - SELENIUM_GRID_HOST=selenium-hub - BINDING_VERSION=${BINDING_VERSION} - SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} + - TEST_UPLOAD_DOWNLOAD_REMOTE=${TEST_UPLOAD_DOWNLOAD_REMOTE:-false} - TEST_NODE_RELAY=${TEST_NODE_RELAY} - ANDROID_PLATFORM_API=${ANDROID_PLATFORM_API} - TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST} diff --git a/tests/k8s/make/dynamic_grid_test.sh b/tests/k8s/make/dynamic_grid_test.sh index 303eca6af..5376a5a7b 100755 --- a/tests/k8s/make/dynamic_grid_test.sh +++ b/tests/k8s/make/dynamic_grid_test.sh @@ -237,11 +237,12 @@ export SELENIUM_GRID_PORT=${GRID_LOCAL_PORT} export SELENIUM_GRID_USERNAME=${GRID_USERNAME} export SELENIUM_GRID_PASSWORD=${GRID_PASSWORD} export SELENIUM_GRID_TEST_HEADLESS=${SELENIUM_GRID_TEST_HEADLESS:-"false"} -# The browser runs in its own Job Pod and downloads into it. The Node forwards the managed -# download commands to the browser session instead of serving them from its own Pod local -# directory (SeleniumHQ/selenium#17914), so the files are retrievable from where the browser -# stored them. Enable managed downloads so the remote download verification exercises that path. -export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"true"} +# The browser runs in its own Job Pod and downloads into it. The Node can forward the managed +# download commands to the browser session (SeleniumHQ/selenium#17914), but that forwarding is +# only available in Selenium >= 4.48.0. Until the Grid base includes it, keep managed downloads +# and the remote upload/download verification off so this lane does not depend on the unreleased fix. +export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"false"} +export TEST_UPLOAD_DOWNLOAD_REMOTE=${TEST_UPLOAD_DOWNLOAD_REMOTE:-"false"} export TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST:-"0"} export BINDING_VERSION=${BINDING_VERSION} export BASE_VERSION=${BASE_VERSION} From a8e803668659d509ebd142418050fcdc0e5d53e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 13:35:40 +0000 Subject: [PATCH 5/7] fix(relay): enable managed downloads on the relay Node so downloads match The Relay lane failed with managed downloads on because only the relay target (standalone) and the client had it enabled, not the relay Node itself. With the Node's slot not advertising se:downloadsEnabled, a session created with enable_downloads had no matching slot and failed, taking the whole lane down. Enable managed downloads on node-relay-standalone too, so its slot advertises se:downloadsEnabled, the request matches, and the Node forwards the file operations to the standalone browser it relays into (SeleniumHQ/selenium#17914). Also drop the TEST_UPLOAD_DOWNLOAD_REMOTE version gate added earlier: CI builds on the Selenium nightly, which already carries the fix, so the remote upload/download verification runs on Relay and Kubernetes as intended. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- Makefile | 6 ++++-- .../Hub_Node/node-kubernetes-deployment.yaml | 2 ++ .../DynamicGrid/Standalone/standalone-kubernetes.yaml | 2 ++ tests/SeleniumTests/__init__.py | 11 ----------- tests/docker-compose-v3-test-node-relay.yml | 7 ++++--- tests/k8s/make/dynamic_grid_test.sh | 11 +++++------ 6 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 61c2590e7..b2a220ca6 100644 --- a/Makefile +++ b/Makefile @@ -1202,7 +1202,8 @@ test_node_relay: hub node_base standalone_firefox echo BASE_VERSION=$(BASE_VERSION) >> .env ; \ if [ $$node = "Android" ] ; then \ echo BROWSER=firefox >> .env \ - && echo BROWSER_NAME=firefox >> .env ; \ + && echo BROWSER_NAME=firefox >> .env \ + && echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=false >> .env ; \ fi ; \ if [ $$node = "NodeChrome" ] ; then \ echo BROWSER=chrome >> .env \ @@ -1219,7 +1220,8 @@ test_node_relay: hub node_base standalone_firefox fi ; \ if [ $$node = "NodeFirefox" ] ; then \ echo BROWSER=firefox >> .env \ - && echo BROWSER_NAME=firefox >> .env ; \ + && echo BROWSER_NAME=firefox >> .env \ + && echo SELENIUM_ENABLE_MANAGED_DOWNLOADS=$(or $(SELENIUM_ENABLE_MANAGED_DOWNLOADS), true) >> .env ; \ fi ; \ export $$(cat .env | xargs) ; \ envsubst < relay_config.toml > ./videos/relay_config.toml ; \ diff --git a/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml b/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml index 25fda268f..6daf3f47c 100644 --- a/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml +++ b/kubernetes/DynamicGrid/Hub_Node/node-kubernetes-deployment.yaml @@ -40,6 +40,8 @@ spec: value: "10" - name: SE_RECORD_VIDEO value: "true" + - name: SE_NODE_ENABLE_MANAGED_DOWNLOADS + value: "true" resources: requests: memory: "512Mi" diff --git a/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml b/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml index abf27e066..66bd36fa0 100644 --- a/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml +++ b/kubernetes/DynamicGrid/Standalone/standalone-kubernetes.yaml @@ -40,6 +40,8 @@ spec: value: "10" - name: SE_RECORD_VIDEO value: "true" + - name: SE_NODE_ENABLE_MANAGED_DOWNLOADS + value: "true" resources: requests: memory: "512Mi" diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py index 67d549e97..4756851fb 100644 --- a/tests/SeleniumTests/__init__.py +++ b/tests/SeleniumTests/__init__.py @@ -30,11 +30,6 @@ TEST_PARALLEL_COUNT = int(os.environ.get('TEST_PARALLEL_COUNT', 5)) TEST_DELAY_AFTER_TEST = int(os.environ.get('TEST_DELAY_AFTER_TEST', 0)) TEST_NODE_RELAY = os.environ.get('TEST_NODE_RELAY', 'false') -# Remote file upload (LocalFileDetector) and managed-download retrieval only work when the Node -# forwards the file operations to the browser session. That forwarding is unconditional for Docker -# Dynamic Grid, but for Relay and Kubernetes sessions it requires SeleniumHQ/selenium#17914 -# (Selenium >= 4.48.0). Those harnesses keep this off until the Grid base includes the fix. -TEST_UPLOAD_DOWNLOAD_REMOTE = os.environ.get('TEST_UPLOAD_DOWNLOAD_REMOTE', 'true').lower() == 'true' TEST_ANDROID_PLATFORM_API = os.environ.get('ANDROID_PLATFORM_API') TEST_PLATFORMS = os.environ.get('TEST_PLATFORMS', 'linux/amd64') TEST_FIREFOX_INSTALL_LANG_PACKAGE = os.environ.get('TEST_FIREFOX_INSTALL_LANG_PACKAGE', 'false').lower() == 'true' @@ -132,10 +127,6 @@ def test_upload_file(self): # Docker/Kubernetes and Relay) the Node forwards the upload to the session so the file lands # where sendKeys runs. Use LocalFileDetector so the client-side file is transferred remotely. # See SeleniumHQ/selenium#17914. - if not TEST_UPLOAD_DOWNLOAD_REMOTE: - self.skipTest( - "Remote file upload/download forwarding is disabled for this deployment (requires Selenium >= 4.48.0)" - ) if TEST_NODE_RELAY == 'Android': self.skipTest("HTML file upload via LocalFileDetector is not applicable to the Android emulator relay") driver.file_detector = LocalFileDetector() @@ -178,8 +169,6 @@ def test_download_file(self): # the session, so the file is fetched from where the browser stored it. Asserting the # retrieved file exists and is non-empty exercises that forwarding path. # See SeleniumHQ/selenium#17914. - if not TEST_UPLOAD_DOWNLOAD_REMOTE: - return download_dir = tempfile.mkdtemp() try: driver.download_file(file_name, download_dir) diff --git a/tests/docker-compose-v3-test-node-relay.yml b/tests/docker-compose-v3-test-node-relay.yml index 900ce198b..81d954708 100644 --- a/tests/docker-compose-v3-test-node-relay.yml +++ b/tests/docker-compose-v3-test-node-relay.yml @@ -11,6 +11,10 @@ services: - SE_EVENT_BUS_HOST=selenium-hub - SE_LOG_LEVEL=${LOG_LEVEL} - GENERATE_CONFIG=false + # The relay Node must also advertise managed downloads so a session requesting + # se:downloadsEnabled matches its slot; the Node then forwards the file operations to the + # standalone browser it relays into (SeleniumHQ/selenium#17914). + - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} standalone: image: ${NAMESPACE}/standalone-${BROWSER}:${TAG} @@ -19,8 +23,6 @@ services: - SE_OPTS=--enable-cdp true --enable-managed-downloads ${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} - SE_NODE_ENABLE_CDP=true - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} - # Managed downloads over Relay needs SeleniumHQ/selenium#17914 (Selenium >= 4.48.0); it stays - # off by default and is exercised once the Grid base includes the fix. selenium-hub: image: ${NAMESPACE}/hub:${TAG} @@ -50,7 +52,6 @@ services: - SELENIUM_GRID_HOST=selenium-hub - BINDING_VERSION=${BINDING_VERSION} - SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} - - TEST_UPLOAD_DOWNLOAD_REMOTE=${TEST_UPLOAD_DOWNLOAD_REMOTE:-false} - TEST_NODE_RELAY=${TEST_NODE_RELAY} - ANDROID_PLATFORM_API=${ANDROID_PLATFORM_API} - TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST} diff --git a/tests/k8s/make/dynamic_grid_test.sh b/tests/k8s/make/dynamic_grid_test.sh index 5376a5a7b..303eca6af 100755 --- a/tests/k8s/make/dynamic_grid_test.sh +++ b/tests/k8s/make/dynamic_grid_test.sh @@ -237,12 +237,11 @@ export SELENIUM_GRID_PORT=${GRID_LOCAL_PORT} export SELENIUM_GRID_USERNAME=${GRID_USERNAME} export SELENIUM_GRID_PASSWORD=${GRID_PASSWORD} export SELENIUM_GRID_TEST_HEADLESS=${SELENIUM_GRID_TEST_HEADLESS:-"false"} -# The browser runs in its own Job Pod and downloads into it. The Node can forward the managed -# download commands to the browser session (SeleniumHQ/selenium#17914), but that forwarding is -# only available in Selenium >= 4.48.0. Until the Grid base includes it, keep managed downloads -# and the remote upload/download verification off so this lane does not depend on the unreleased fix. -export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"false"} -export TEST_UPLOAD_DOWNLOAD_REMOTE=${TEST_UPLOAD_DOWNLOAD_REMOTE:-"false"} +# The browser runs in its own Job Pod and downloads into it. The Node forwards the managed +# download commands to the browser session instead of serving them from its own Pod local +# directory (SeleniumHQ/selenium#17914), so the files are retrievable from where the browser +# stored them. Enable managed downloads so the remote download verification exercises that path. +export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"true"} export TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST:-"0"} export BINDING_VERSION=${BINDING_VERSION} export BASE_VERSION=${BASE_VERSION} From 4a23e94aa33d85878dece312824e706cf459707b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 16:01:44 +0000 Subject: [PATCH 6/7] fix(relay): advertise se:downloadsEnabled in the relay stereotype The Relay lane still failed session creation with "could not find matching slot": every Firefox test errored in setUp. Enabling managed downloads on the relay Node is not enough, because Selenium's slot matcher requires the slot stereotype to carry se:downloadsEnabled when the request asks for it, and a relay stereotype is taken verbatim from relay_config.toml rather than augmented by --enable-managed-downloads the way a regular Node's slots are. Add se:downloadsEnabled to the relay stereotype, templated from SELENIUM_ENABLE_MANAGED_DOWNLOADS so it is true for the desktop Firefox relay and false for the Android relay. The client's managed-download request now matches the relay slot; the session is created and the Node forwards the file operations to the standalone browser it relays into. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- tests/relay_config.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/relay_config.toml b/tests/relay_config.toml index 3f89202d4..1d4c05b49 100755 --- a/tests/relay_config.toml +++ b/tests/relay_config.toml @@ -12,6 +12,9 @@ max-sessions = 1 [relay] url = "http://standalone:4444/wd/hub" status-endpoint = "/status" +# se:downloadsEnabled must be part of the relay stereotype so a session requesting managed +# downloads matches this slot. Unlike a regular Node, --enable-managed-downloads does not add it to +# a relay stereotype, which is taken verbatim from this config. See SeleniumHQ/selenium#17914. configs = [ - '3', '{"browserName":"${BROWSER_NAME}","platformName":"linux"}' + '3', '{"browserName":"${BROWSER_NAME}","platformName":"linux","se:downloadsEnabled":${SELENIUM_ENABLE_MANAGED_DOWNLOADS}}' ] From 407bdd4555d3157a04a4f83719a4fbdd07ad2382 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 18:21:03 +0000 Subject: [PATCH 7/7] chore(relay): default managed downloads to true and trim comments Make the SELENIUM_ENABLE_MANAGED_DOWNLOADS fallback true across the relay compose services so an unset value enables managed downloads by default, matching the client default; the Android relay still sets it false explicitly. Remove verbose explanatory comments. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VxGB7ifcYFULc8hDiwPfUf --- tests/SeleniumTests/__init__.py | 12 ++---------- tests/docker-compose-v3-test-node-relay.yml | 11 ++++------- tests/k8s/make/dynamic_grid_test.sh | 5 +---- tests/relay_config.toml | 3 --- 4 files changed, 7 insertions(+), 24 deletions(-) diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py index 4756851fb..0f371af06 100644 --- a/tests/SeleniumTests/__init__.py +++ b/tests/SeleniumTests/__init__.py @@ -122,11 +122,7 @@ def test_play_video(self): def test_upload_file(self): driver = self.driver - # A local file must be transferred to the machine running the browser before the browser - # can select it. When the browser does not share the Node filesystem (Dynamic Grid on - # Docker/Kubernetes and Relay) the Node forwards the upload to the session so the file lands - # where sendKeys runs. Use LocalFileDetector so the client-side file is transferred remotely. - # See SeleniumHQ/selenium#17914. + # LocalFileDetector transfers the client-side file to the machine running the browser. if TEST_NODE_RELAY == 'Android': self.skipTest("HTML file upload via LocalFileDetector is not applicable to the Android emulator relay") driver.file_detector = LocalFileDetector() @@ -164,11 +160,7 @@ def test_download_file(self): lambda d: len(d.get_downloadable_files()) > 0 and str(d.get_downloadable_files()[0]).endswith(file_name) ) self.assertTrue(str(driver.get_downloadable_files()[0]).endswith(file_name)) - # Retrieve the managed download to the client. When the browser does not share the Node - # filesystem (Dynamic Grid on Docker/Kubernetes and Relay) the Node forwards downloadFile to - # the session, so the file is fetched from where the browser stored it. Asserting the - # retrieved file exists and is non-empty exercises that forwarding path. - # See SeleniumHQ/selenium#17914. + # Retrieve the managed download to the client to exercise the remote file forwarding. download_dir = tempfile.mkdtemp() try: driver.download_file(file_name, download_dir) diff --git a/tests/docker-compose-v3-test-node-relay.yml b/tests/docker-compose-v3-test-node-relay.yml index 81d954708..e4e59c1c5 100644 --- a/tests/docker-compose-v3-test-node-relay.yml +++ b/tests/docker-compose-v3-test-node-relay.yml @@ -11,18 +11,15 @@ services: - SE_EVENT_BUS_HOST=selenium-hub - SE_LOG_LEVEL=${LOG_LEVEL} - GENERATE_CONFIG=false - # The relay Node must also advertise managed downloads so a session requesting - # se:downloadsEnabled matches its slot; the Node then forwards the file operations to the - # standalone browser it relays into (SeleniumHQ/selenium#17914). - - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} + - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-true} standalone: image: ${NAMESPACE}/standalone-${BROWSER}:${TAG} shm_size: 2gb environment: - - SE_OPTS=--enable-cdp true --enable-managed-downloads ${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} + - SE_OPTS=--enable-cdp true --enable-managed-downloads ${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-true} - SE_NODE_ENABLE_CDP=true - - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} + - SE_NODE_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-true} selenium-hub: image: ${NAMESPACE}/hub:${TAG} @@ -51,7 +48,7 @@ services: - RUN_IN_DOCKER_COMPOSE=true - SELENIUM_GRID_HOST=selenium-hub - BINDING_VERSION=${BINDING_VERSION} - - SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-false} + - SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-true} - TEST_NODE_RELAY=${TEST_NODE_RELAY} - ANDROID_PLATFORM_API=${ANDROID_PLATFORM_API} - TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST} diff --git a/tests/k8s/make/dynamic_grid_test.sh b/tests/k8s/make/dynamic_grid_test.sh index 303eca6af..0155ee86f 100755 --- a/tests/k8s/make/dynamic_grid_test.sh +++ b/tests/k8s/make/dynamic_grid_test.sh @@ -237,10 +237,7 @@ export SELENIUM_GRID_PORT=${GRID_LOCAL_PORT} export SELENIUM_GRID_USERNAME=${GRID_USERNAME} export SELENIUM_GRID_PASSWORD=${GRID_PASSWORD} export SELENIUM_GRID_TEST_HEADLESS=${SELENIUM_GRID_TEST_HEADLESS:-"false"} -# The browser runs in its own Job Pod and downloads into it. The Node forwards the managed -# download commands to the browser session instead of serving them from its own Pod local -# directory (SeleniumHQ/selenium#17914), so the files are retrievable from where the browser -# stored them. Enable managed downloads so the remote download verification exercises that path. +# The Node forwards managed downloads to the browser Job Pod, so they are retrievable here. export SELENIUM_ENABLE_MANAGED_DOWNLOADS=${SELENIUM_ENABLE_MANAGED_DOWNLOADS:-"true"} export TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST:-"0"} export BINDING_VERSION=${BINDING_VERSION} diff --git a/tests/relay_config.toml b/tests/relay_config.toml index 1d4c05b49..af58f5665 100755 --- a/tests/relay_config.toml +++ b/tests/relay_config.toml @@ -12,9 +12,6 @@ max-sessions = 1 [relay] url = "http://standalone:4444/wd/hub" status-endpoint = "/status" -# se:downloadsEnabled must be part of the relay stereotype so a session requesting managed -# downloads matches this slot. Unlike a regular Node, --enable-managed-downloads does not add it to -# a relay stereotype, which is taken verbatim from this config. See SeleniumHQ/selenium#17914. configs = [ '3', '{"browserName":"${BROWSER_NAME}","platformName":"linux","se:downloadsEnabled":${SELENIUM_ENABLE_MANAGED_DOWNLOADS}}' ]