diff --git a/.gitlab/services.yml b/.gitlab/services.yml index 4f6a8971129..445ced95bf9 100644 --- a/.gitlab/services.yml +++ b/.gitlab/services.yml @@ -160,3 +160,6 @@ azurite: name: registry.ddbuild.io/images/mirror/azure-storage/azurite:3.34.0 alias: azurite + selenium-chrome: + name: registry.ddbuild.io/images/mirror/selenium/standalone-chrome:latest + alias: selenium-chrome diff --git a/docker-compose.yml b/docker-compose.yml index 960e994e0d6..5665c98ae7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -190,6 +190,15 @@ services: - "127.0.0.1:10002:10002" + selenium-chrome: + image: selenium/standalone-chrome:latest + platform: linux/amd64 + network_mode: host + environment: + - SE_NODE_MAX_SESSIONS=2 + - SE_VNC_NO_PASSWORD=1 + shm_size: 2g + testrunner: # DEV uncomment to test local changes to the Dockerfile # build: diff --git a/tests/ci_visibility/suitespec.yml b/tests/ci_visibility/suitespec.yml index a4194d3b0c7..884e6c5cbfd 100644 --- a/tests/ci_visibility/suitespec.yml +++ b/tests/ci_visibility/suitespec.yml @@ -157,6 +157,8 @@ suites: snapshot: true selenium: parallelism: 2 + env: + SELENIUM_GRID_URL: http://selenium-chrome:4444 paths: - '@bootstrap' - '@core' @@ -170,6 +172,8 @@ suites: - tests/contrib/selenium/* - tests/snapshots/test_selenium* snapshot: true + services: + - selenium-chrome unittest: parallelism: 2 paths: diff --git a/tests/contrib/selenium/test_selenium_chrome.py b/tests/contrib/selenium/test_selenium_chrome.py index 2816b3497b3..eb8c1d23b6d 100644 --- a/tests/contrib/selenium/test_selenium_chrome.py +++ b/tests/contrib/selenium/test_selenium_chrome.py @@ -1,8 +1,9 @@ """Tests for selenium + RUM integration -IMPORTANT NOTE: these tests only reliably work on Linux/x86_64 due to some annoyingly picky issues with installing -Selenium and a working browser/webdriver combination on non-x86_64 architectures (at time of writing, at least, -Selenium's webdriver-manager doesn't support Linux on non-x86_64). +Uses a Selenium Grid (selenium/standalone-chrome, linux/amd64) via webdriver.Remote so that +tests work on any host architecture including arm64. The grid URL is controlled by the +SELENIUM_GRID_URL environment variable (default: http://localhost:4444 for local runs via +docker-compose network_mode: host; set to http://selenium-chrome:4444 in CI). """ import http.server @@ -10,7 +11,6 @@ import multiprocessing import os from pathlib import Path -import platform import socketserver import subprocess import textwrap @@ -45,6 +45,23 @@ "start", ] +# Selenium Grid endpoint — standalone-chrome runs with network_mode: host so it binds on localhost +SELENIUM_GRID_URL = os.environ.get("SELENIUM_GRID_URL", "http://localhost:4444") + +_SELENIUM_DRIVER_SETUP = f"""\ + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.chrome.options import Options + + SELENIUM_GRID_URL = "{SELENIUM_GRID_URL}" + + def _make_driver(): + options = Options() + options.add_argument("--headless") + options.add_argument("--no-sandbox") + return webdriver.Remote(command_executor=SELENIUM_GRID_URL, options=options) +""" + @pytest.fixture def _http_server(scope="function"): @@ -74,39 +91,29 @@ def _run_server(): @snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES) -@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64") def test_selenium_chrome_pytest_rum_enabled(_http_server, testdir, git_repo): selenium_test_script = textwrap.dedent( - """ - from pathlib import Path + _SELENIUM_DRIVER_SETUP + + """ + def test_selenium_local_pass(): + with _make_driver() as driver: + url = "http://localhost:8079/rum_enabled/page_1.html" - from selenium import webdriver - from selenium.webdriver.common.by import By - from selenium.webdriver.chrome.options import Options + driver.get(url) - def test_selenium_local_pass(): - options = Options() - options.add_argument("--headless") - options.add_argument("--no-sandbox") + assert driver.title == "Page 1" - with webdriver.Chrome(options=options) as driver: - url = "http://localhost:8079/rum_enabled/page_1.html" + link_2 = driver.find_element(By.LINK_TEXT, "Page 2") - driver.get(url) + link_2.click() - assert driver.title == "Page 1" + assert driver.title == "Page 2" - link_2 = driver.find_element(By.LINK_TEXT, "Page 2") + link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") + link_1.click() - link_2.click() - - assert driver.title == "Page 2" - - link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") - link_1.click() - - assert driver.title == "Page 1" - """ + assert driver.title == "Page 1" + """ ) testdir.makepyfile(test_selenium=selenium_test_script) subprocess.run( @@ -127,39 +134,29 @@ def test_selenium_local_pass(): @snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES) -@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64") def test_selenium_chrome_pytest_rum_disabled(_http_server, testdir, git_repo): selenium_test_script = textwrap.dedent( - """ - from pathlib import Path + _SELENIUM_DRIVER_SETUP + + """ + def test_selenium_local_pass(): + with _make_driver() as driver: + url = "http://localhost:8079/rum_disabled/page_1.html" - from selenium import webdriver - from selenium.webdriver.common.by import By - from selenium.webdriver.chrome.options import Options + driver.get(url) - def test_selenium_local_pass(): - options = Options() - options.add_argument("--headless") - options.add_argument("--no-sandbox") + assert driver.title == "Page 1" - with webdriver.Chrome(options=options) as driver: - url = "http://localhost:8079/rum_disabled/page_1.html" + link_2 = driver.find_element(By.LINK_TEXT, "Page 2") - driver.get(url) + link_2.click() - assert driver.title == "Page 1" + assert driver.title == "Page 2" - link_2 = driver.find_element(By.LINK_TEXT, "Page 2") + link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") + link_1.click() - link_2.click() - - assert driver.title == "Page 2" - - link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") - link_1.click() - - assert driver.title == "Page 1" - """ + assert driver.title == "Page 1" + """ ) testdir.makepyfile(test_selenium=selenium_test_script) subprocess.run( @@ -180,42 +177,32 @@ def test_selenium_local_pass(): @snapshot(ignores=SELENIUM_SNAPSHOT_IGNORES) -@pytest.mark.skipif(platform.machine() != "x86_64", reason="Selenium Chrome tests only run on x86_64") def test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags(_http_server, testdir, git_repo): selenium_test_script = textwrap.dedent( - """ - from pathlib import Path + _SELENIUM_DRIVER_SETUP + + """ + from ddtrace.contrib.internal.selenium.patch import unpatch - from selenium import webdriver - from selenium.webdriver.common.by import By - from selenium.webdriver.chrome.options import Options + def test_selenium_local_unpatch(): + unpatch() + with _make_driver() as driver: + url = "http://localhost:8079/rum_disabled/page_1.html" - from ddtrace.contrib.internal.selenium.patch import unpatch + driver.get(url) - def test_selenium_local_unpatch(): - unpatch() - options = Options() - options.add_argument("--headless") - options.add_argument("--no-sandbox") + assert driver.title == "Page 1" - with webdriver.Chrome(options=options) as driver: - url = "http://localhost:8079/rum_disabled/page_1.html" + link_2 = driver.find_element(By.LINK_TEXT, "Page 2") - driver.get(url) + link_2.click() - assert driver.title == "Page 1" + assert driver.title == "Page 2" - link_2 = driver.find_element(By.LINK_TEXT, "Page 2") + link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") + link_1.click() - link_2.click() - - assert driver.title == "Page 2" - - link_1 = driver.find_element(By.LINK_TEXT, "Back to page 1.") - link_1.click() - - assert driver.title == "Page 1" - """ + assert driver.title == "Page 1" + """ ) testdir.makepyfile(test_selenium=selenium_test_script) subprocess.run( diff --git a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json index ac5ff587137..efcb4d8ddc9 100644 --- a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json +++ b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_disabled.json @@ -178,8 +178,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, "process_id": 13623, - "test.source.end": 29, - "test.source.start": 7 + "test.source.end": 31, + "test.source.start": 13 }, "duration": 1035420043, "start": 1733243045320806200 diff --git a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json index fe2f392d919..271c70834db 100644 --- a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json +++ b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_rum_enabled.json @@ -179,8 +179,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, "process_id": 13453, - "test.source.end": 29, - "test.source.start": 7 + "test.source.end": 31, + "test.source.start": 13 }, "duration": 1056245299, "start": 1733243043078204615 diff --git a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json index 3ce27b6bed5..812051651d5 100644 --- a/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json +++ b/tests/snapshots/test_selenium_chrome.test_selenium_chrome_pytest_unpatch_does_not_record_selenium_tags.json @@ -173,8 +173,8 @@ "_dd.tracer_kr": 1.0, "_sampling_priority_v1": 1, "process_id": 12386, - "test.source.end": 32, - "test.source.start": 9 + "test.source.end": 34, + "test.source.start": 15 }, "duration": 534785315, "start": 1733242640750308571