Skip to content

Commit d5a12d8

Browse files
authored
chore(selenium): patch selenium in v3 pytest plugin for browser test tags (#16531)
## Description The v3 pytest plugin (`ddtrace.testing.internal.pytest`) does not call `_patch_all()` by default, so the Selenium integration was never applied — browser tests were missing `test.is_browser`, `test.browser.*` tags. This PR explicitly patches Selenium in `pytest_sessionstart` when the ddtrace trace filter is active (which is the default for the v3 plugin), ensuring browser test visibility tags are set even without `--ddtrace-patch-all`. ## Changes - **`ddtrace/testing/internal/pytest/plugin.py`**: Patch Selenium in `pytest_sessionstart` when `enable_ddtrace_trace_filter` is true but `enable_all_ddtrace_integrations` is false. - **`ddtrace/contrib/internal/selenium/patch.py`**: Added docstring explaining how the integration works with both v2 and v3 plugins. - **`tests/contrib/selenium/test_selenium_chrome.py`**: Added `test_selenium_v3_plugin_tags` to verify browser tags are set with the v3 plugin. Existing snapshot tests are pinned to the old plugin (`DD_PYTEST_USE_NEW_PLUGIN=false`) since they rely on agent traces. ## Testing - New test `test_selenium_v3_plugin_tags` uses `pytester` with a mocked `WebDriver` to verify `test.is_browser`, `test.browser.name`, and `test.browser.version` tags are correctly set. ## Risks None — the Selenium patch is wrapped in a try/except and only applies in the v3 plugin path. Co-authored-by: federico.mon <federico.mon@datadoghq.com>
1 parent 4d6a8ee commit d5a12d8

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

ddtrace/testing/internal/pytest/plugin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,15 @@ def pytest_sessionstart(self, session: pytest.Session) -> None:
320320

321321
if self.enable_all_ddtrace_integrations:
322322
enable_all_ddtrace_integrations()
323+
elif self.enable_ddtrace_trace_filter:
324+
# Patch Selenium so browser tests get test visibility tags (test.is_browser, test.browser.*).
325+
# We create a root span with type=test in trace_context(); Selenium integration checks for that span.
326+
try:
327+
from ddtrace.contrib.internal.selenium.patch import patch as patch_selenium
328+
329+
patch_selenium()
330+
except Exception:
331+
log.debug("Could not patch Selenium for test visibility", exc_info=True)
323332

324333
def pytest_sessionfinish(self, session: pytest.Session) -> None:
325334
# With xdist, the main process does not execute tests, so we cannot rely on the normal `session.get_status()`

tests/contrib/selenium/test_selenium_chrome.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import http.server
9+
import json
910
import multiprocessing
1011
import os
1112
from pathlib import Path
@@ -118,6 +119,7 @@ def test_selenium_local_pass():
118119
CI_PROJECT_DIR=str(testdir.tmpdir),
119120
DD_CIVISIBILITY_AGENTLESS_ENABLED="false",
120121
_DD_CIVISIBILITY_DISABLE_EVP_PROXY="true",
122+
# Snapshot test expects traces from the agent; v3 plugin uses TestOptWriter and does not send them.
121123
DD_PYTEST_USE_NEW_PLUGIN="false",
122124
)
123125
),
@@ -170,6 +172,7 @@ def test_selenium_local_pass():
170172
CI_PROJECT_DIR=str(testdir.tmpdir),
171173
DD_CIVISIBILITY_AGENTLESS_ENABLED="false",
172174
_DD_CIVISIBILITY_DISABLE_EVP_PROXY="true",
175+
# Snapshot test expects traces from the agent; v3 plugin uses TestOptWriter and does not send them.
173176
DD_PYTEST_USE_NEW_PLUGIN="false",
174177
)
175178
),
@@ -225,7 +228,70 @@ def test_selenium_local_unpatch():
225228
CI_PROJECT_DIR=str(testdir.tmpdir),
226229
DD_CIVISIBILITY_AGENTLESS_ENABLED="false",
227230
_DD_CIVISIBILITY_DISABLE_EVP_PROXY="true",
231+
# Snapshot test expects traces from the agent; v3 plugin uses TestOptWriter and does not send them.
228232
DD_PYTEST_USE_NEW_PLUGIN="false",
229233
)
230234
),
231235
)
236+
237+
238+
def test_selenium_v3_plugin_tags(tmp_path, pytester, git_repo):
239+
events_file = tmp_path / "events.json"
240+
241+
# conftest.py that captures events emitted by the v3 plugin to a JSON file
242+
pytester.makeconftest(
243+
f"""
244+
import json, atexit
245+
from ddtrace.testing.internal.writer import TestOptWriter
246+
247+
_events = []
248+
_orig_put = TestOptWriter.put_event
249+
def _capture(self, event):
250+
_events.append(event)
251+
return _orig_put(self, event)
252+
TestOptWriter.put_event = _capture
253+
254+
@atexit.register
255+
def _dump():
256+
with open(r"{events_file}", "w") as f:
257+
json.dump(_events, f, default=str)
258+
"""
259+
)
260+
261+
pytester.makepyfile(
262+
test_selenium="""
263+
from selenium.webdriver.remote.webdriver import WebDriver
264+
265+
def test_selenium_browser_tags():
266+
driver = WebDriver.__new__(WebDriver)
267+
driver.caps = {"browserName": "chrome", "browserVersion": "120.0"}
268+
driver.add_cookie = lambda cookie: None
269+
driver.execute = lambda *a, **kw: None
270+
271+
driver.get("http://example.com")
272+
"""
273+
)
274+
275+
subprocess.run(
276+
["pytest", "--ddtrace", "-v", "-s"],
277+
cwd=str(pytester.path),
278+
env=_get_default_ci_env_vars(
279+
dict(
280+
DD_API_KEY="foobar.baz",
281+
DD_CIVISIBILITY_ITR_ENABLED="false",
282+
DD_PATCH_MODULES="sqlite3:false",
283+
CI_PROJECT_DIR=str(pytester.path),
284+
DD_CIVISIBILITY_AGENTLESS_ENABLED="false",
285+
_DD_CIVISIBILITY_DISABLE_EVP_PROXY="true",
286+
DD_PYTEST_USE_NEW_PLUGIN="true",
287+
)
288+
),
289+
)
290+
291+
events = json.loads(events_file.read_text())
292+
test_events = [e for e in events if e["type"] == "test"]
293+
meta = test_events[0]["content"]["meta"]
294+
assert meta["test.is_browser"] == "true"
295+
assert meta["test.browser.driver"] == "selenium"
296+
assert meta["test.browser.name"] == "chrome"
297+
assert meta["test.browser.version"] == "120.0"

0 commit comments

Comments
 (0)