Skip to content

Commit caa8f79

Browse files
authored
chore(selenium): use dockerized selenium setup for tests (#17708)
## Description The selenium tests were previously gated behind a `platform.machine() == "x86_64"` check because `selenium-manager` (bundled with the `selenium` package) has no binary for `linux/aarch64`, making them impossible to run locally on Apple Silicon. This PR replaces the local `webdriver.Chrome` setup with `webdriver.Remote` pointing at a `selenium/standalone-chrome` Docker service (linux/amd64). This decouples the test runner architecture from the browser architecture, so tests run on any host. Changes: - **`docker-compose.yml`**: adds a `selenium-chrome` service (`selenium/standalone-chrome:latest`, `platform: linux/amd64`, `network_mode: host`) so local runs via `scripts/ddtest` work on arm64 hosts - **`.gitlab/services.yml`**: registers `selenium-chrome` as a named GitLab CI service pointing at the internal registry mirror - **`tests/ci_visibility/suitespec.yml`**: adds `selenium-chrome` to the selenium suite's required services and sets `SELENIUM_GRID_URL=http://selenium-chrome:4444` for CI (where services are accessed by hostname alias rather than `localhost`) - **`test_selenium_chrome.py`**: removes the `x86_64` skipif guards and the `platform` import; extracts a `_make_driver()` helper that uses `webdriver.Remote` with `SELENIUM_GRID_URL` (defaults to `http://localhost:4444` for local runs) - **Snapshots**: updated `test.source.start` / `test.source.end` metrics to reflect the refactored test script line numbers; also added `meta._dd.svc_src` and `meta._dd.tags.process` to the snapshot ignore list (new fields emitted by newer ddtrace versions) ## Testing - Ran all 4 selenium tests locally via `scripts/run-tests` with the `selenium-chrome` Docker service active — all pass - `test_selenium_v3_plugin_tags` (mock-based, no real browser) continues to pass as before ## Risks - `selenium/standalone-chrome:latest` is a mutable tag — if the image is updated it may change browser/driver versions. The snapshot ignore list already excludes `test.browser.version` and `test.browser.driver_version` so this is safe. ## Additional Notes The `SELENIUM_GRID_URL` env var allows the grid endpoint to be overridden without code changes, which is useful if the service is ever moved or renamed. Co-authored-by: federico.mon <federico.mon@datadoghq.com>
1 parent 84284e9 commit caa8f79

7 files changed

Lines changed: 87 additions & 84 deletions

.gitlab/services.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,6 @@
160160
azurite:
161161
name: registry.ddbuild.io/images/mirror/azure-storage/azurite:3.34.0
162162
alias: azurite
163+
selenium-chrome:
164+
name: registry.ddbuild.io/images/mirror/selenium/standalone-chrome:latest
165+
alias: selenium-chrome

docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,15 @@ services:
190190
- "127.0.0.1:10002:10002"
191191

192192

193+
selenium-chrome:
194+
image: selenium/standalone-chrome:latest
195+
platform: linux/amd64
196+
network_mode: host
197+
environment:
198+
- SE_NODE_MAX_SESSIONS=2
199+
- SE_VNC_NO_PASSWORD=1
200+
shm_size: 2g
201+
193202
testrunner:
194203
# DEV uncomment to test local changes to the Dockerfile
195204
# build:

tests/ci_visibility/suitespec.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ suites:
157157
snapshot: true
158158
selenium:
159159
parallelism: 2
160+
env:
161+
SELENIUM_GRID_URL: http://selenium-chrome:4444
160162
paths:
161163
- '@bootstrap'
162164
- '@core'
@@ -170,6 +172,8 @@ suites:
170172
- tests/contrib/selenium/*
171173
- tests/snapshots/test_selenium*
172174
snapshot: true
175+
services:
176+
- selenium-chrome
173177
unittest:
174178
parallelism: 2
175179
paths:

tests/contrib/selenium/test_selenium_chrome.py

Lines changed: 65 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"""Tests for selenium + RUM integration
22
3-
IMPORTANT NOTE: these tests only reliably work on Linux/x86_64 due to some annoyingly picky issues with installing
4-
Selenium and a working browser/webdriver combination on non-x86_64 architectures (at time of writing, at least,
5-
Selenium's webdriver-manager doesn't support Linux on non-x86_64).
3+
Uses a Selenium Grid (selenium/standalone-chrome, linux/amd64) via webdriver.Remote so that
4+
tests work on any host architecture including arm64. The grid URL is controlled by the
5+
SELENIUM_GRID_URL environment variable (default: http://localhost:4444 for local runs via
6+
docker-compose network_mode: host; set to http://selenium-chrome:4444 in CI).
67
"""
78

89
import http.server
910
import json
1011
import multiprocessing
1112
import os
1213
from pathlib import Path
13-
import platform
1414
import socketserver
1515
import subprocess
1616
import textwrap
@@ -45,6 +45,23 @@
4545
"start",
4646
]
4747

48+
# Selenium Grid endpoint — standalone-chrome runs with network_mode: host so it binds on localhost
49+
SELENIUM_GRID_URL = os.environ.get("SELENIUM_GRID_URL", "http://localhost:4444")
50+
51+
_SELENIUM_DRIVER_SETUP = f"""\
52+
from selenium import webdriver
53+
from selenium.webdriver.common.by import By
54+
from selenium.webdriver.chrome.options import Options
55+
56+
SELENIUM_GRID_URL = "{SELENIUM_GRID_URL}"
57+
58+
def _make_driver():
59+
options = Options()
60+
options.add_argument("--headless")
61+
options.add_argument("--no-sandbox")
62+
return webdriver.Remote(command_executor=SELENIUM_GRID_URL, options=options)
63+
"""
64+
4865

4966
@pytest.fixture
5067
def _http_server(scope="function"):
@@ -74,39 +91,29 @@ def _run_server():
7491

7592

7693
@snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES)
77-
@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64")
7894
def test_selenium_chrome_pytest_rum_enabled(_http_server, testdir, git_repo):
7995
selenium_test_script = textwrap.dedent(
80-
"""
81-
from pathlib import Path
96+
_SELENIUM_DRIVER_SETUP
97+
+ """
98+
def test_selenium_local_pass():
99+
with _make_driver() as driver:
100+
url = "http://localhost:8079/rum_enabled/page_1.html"
82101
83-
from selenium import webdriver
84-
from selenium.webdriver.common.by import By
85-
from selenium.webdriver.chrome.options import Options
102+
driver.get(url)
86103
87-
def test_selenium_local_pass():
88-
options = Options()
89-
options.add_argument("--headless")
90-
options.add_argument("--no-sandbox")
104+
assert driver.title == "Page 1"
91105
92-
with webdriver.Chrome(options=options) as driver:
93-
url = "http://localhost:8079/rum_enabled/page_1.html"
106+
link_2 = driver.find_element(By.LINK_TEXT, "Page 2")
94107
95-
driver.get(url)
108+
link_2.click()
96109
97-
assert driver.title == "Page 1"
110+
assert driver.title == "Page 2"
98111
99-
link_2 = driver.find_element(By.LINK_TEXT, "Page 2")
112+
link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.")
113+
link_1.click()
100114
101-
link_2.click()
102-
103-
assert driver.title == "Page 2"
104-
105-
link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.")
106-
link_1.click()
107-
108-
assert driver.title == "Page 1"
109-
"""
115+
assert driver.title == "Page 1"
116+
"""
110117
)
111118
testdir.makepyfile(test_selenium=selenium_test_script)
112119
subprocess.run(
@@ -127,39 +134,29 @@ def test_selenium_local_pass():
127134

128135

129136
@snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES)
130-
@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64")
131137
def test_selenium_chrome_pytest_rum_disabled(_http_server, testdir, git_repo):
132138
selenium_test_script = textwrap.dedent(
133-
"""
134-
from pathlib import Path
139+
_SELENIUM_DRIVER_SETUP
140+
+ """
141+
def test_selenium_local_pass():
142+
with _make_driver() as driver:
143+
url = "http://localhost:8079/rum_disabled/page_1.html"
135144
136-
from selenium import webdriver
137-
from selenium.webdriver.common.by import By
138-
from selenium.webdriver.chrome.options import Options
145+
driver.get(url)
139146
140-
def test_selenium_local_pass():
141-
options = Options()
142-
options.add_argument("--headless")
143-
options.add_argument("--no-sandbox")
147+
assert driver.title == "Page 1"
144148
145-
with webdriver.Chrome(options=options) as driver:
146-
url = "http://localhost:8079/rum_disabled/page_1.html"
149+
link_2 = driver.find_element(By.LINK_TEXT, "Page 2")
147150
148-
driver.get(url)
151+
link_2.click()
149152
150-
assert driver.title == "Page 1"
153+
assert driver.title == "Page 2"
151154
152-
link_2 = driver.find_element(By.LINK_TEXT, "Page 2")
155+
link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.")
156+
link_1.click()
153157
154-
link_2.click()
155-
156-
assert driver.title == "Page 2"
157-
158-
link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.")
159-
link_1.click()
160-
161-
assert driver.title == "Page 1"
162-
"""
158+
assert driver.title == "Page 1"
159+
"""
163160
)
164161
testdir.makepyfile(test_selenium=selenium_test_script)
165162
subprocess.run(
@@ -180,42 +177,32 @@ def test_selenium_local_pass():
180177

181178

182179
@snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES)
183-
@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64")
184180
def test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags(_http_server, testdir, git_repo):
185181
selenium_test_script = textwrap.dedent(
186-
"""
187-
from pathlib import Path
182+
_SELENIUM_DRIVER_SETUP
183+
+ """
184+
from ddtrace.contrib.internal.selenium.patch import unpatch
188185
189-
from selenium import webdriver
190-
from selenium.webdriver.common.by import By
191-
from selenium.webdriver.chrome.options import Options
186+
def test_selenium_local_unpatch():
187+
unpatch()
188+
with _make_driver() as driver:
189+
url = "http://localhost:8079/rum_disabled/page_1.html"
192190
193-
from ddtrace.contrib.internal.selenium.patch import unpatch
191+
driver.get(url)
194192
195-
def test_selenium_local_unpatch():
196-
unpatch()
197-
options = Options()
198-
options.add_argument("--headless")
199-
options.add_argument("--no-sandbox")
193+
assert driver.title == "Page 1"
200194
201-
with webdriver.Chrome(options=options) as driver:
202-
url = "http://localhost:8079/rum_disabled/page_1.html"
195+
link_2 = driver.find_element(By.LINK_TEXT, "Page 2")
203196
204-
driver.get(url)
197+
link_2.click()
205198
206-
assert driver.title == "Page 1"
199+
assert driver.title == "Page 2"
207200
208-
link_2 = driver.find_element(By.LINK_TEXT, "Page 2")
201+
link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.")
202+
link_1.click()
209203
210-
link_2.click()
211-
212-
assert driver.title == "Page 2"
213-
214-
link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.")
215-
link_1.click()
216-
217-
assert driver.title == "Page 1"
218-
"""
204+
assert driver.title == "Page 1"
205+
"""
219206
)
220207
testdir.makepyfile(test_selenium=selenium_test_script)
221208
subprocess.run(

tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@
178178
"_dd.tracer_kr": 1.0,
179179
"_sampling_priority_v1": 1,
180180
"process_id": 13623,
181-
"test.source.end": 29,
182-
"test.source.start": 7
181+
"test.source.end": 31,
182+
"test.source.start": 13
183183
},
184184
"duration": 1035420043,
185185
"start": 1733243045320806200

tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@
179179
"_dd.tracer_kr": 1.0,
180180
"_sampling_priority_v1": 1,
181181
"process_id": 13453,
182-
"test.source.end": 29,
183-
"test.source.start": 7
182+
"test.source.end": 31,
183+
"test.source.start": 13
184184
},
185185
"duration": 1056245299,
186186
"start": 1733243043078204615

tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@
173173
"_dd.tracer_kr": 1.0,
174174
"_sampling_priority_v1": 1,
175175
"process_id": 12386,
176-
"test.source.end": 32,
177-
"test.source.start": 9
176+
"test.source.end": 34,
177+
"test.source.start": 15
178178
},
179179
"duration": 534785315,
180180
"start": 1733242640750308571

0 commit comments

Comments
 (0)