diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 9cab85f421c6d..ad3effcdbb203 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -49,6 +49,28 @@ jobs: run: | bazel test --local_test_jobs 1 --flaky_test_attempts 3 //py:test-remote + selenium-manager-tests: + name: Selenium Manager Tests (${{ matrix.os }}) + needs: build + uses: ./.github/workflows/bazel.yml + strategy: + fail-fast: false + matrix: + os: [ubuntu, windows, macos] + with: + name: Selenium Manager Tests (${{ matrix.os }}) + os: ${{ matrix.os }} + run: | + bazel test \ + --keep_going \ + --build_tests_only \ + --flaky_test_attempts 3 \ + --local_test_jobs 1 \ + --pin_browsers=false \ + --test_size_filters medium \ + --test_tag_filters="manager,-ie,-safari" \ + //py:test-sm + browser-tests: name: Browser Tests needs: build diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 93b7072604b1f..8da871445885d 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -812,6 +812,51 @@ BROWSER_TESTS = { ] ] +# Generate test--sm targets (Selenium Manager integration tests) +# These only run when SE_FORCE_BROWSER_DOWNLOAD=true is set and use +# --pin_browsers=false to test SM download resolution. +[ + py_test_suite( + name = "test-%s-sm" % browser, + size = "medium", + srcs = ["test/selenium/webdriver/%s/%s_service_tests.py" % (browser, browser)], + args = [ + "--instafail", + "-k", + "selenium_manager", + ] + BROWSERS[browser]["args"], + data = BROWSERS[browser]["data"], + env = { + "SE_FORCE_BROWSER_DOWNLOAD": "true", + "SE_SKIP_DRIVER_IN_PATH": "true", + }, + tags = [ + "manager", + "no-sandbox", + "requires-network", + ], + deps = [ + ":init-tree", + ":selenium", + ] + TEST_DEPS, + ) + for browser in [ + "chrome", + "edge", + "firefox", + ] +] + +test_suite( + name = "test-sm", + tags = ["manager"], + tests = [ + ":test-chrome-sm", + ":test-edge-sm", + ":test-firefox-sm", + ], +) + test_suite( name = "test-remote", tags = ["remote"], diff --git a/py/test/selenium/webdriver/chrome/chrome_service_tests.py b/py/test/selenium/webdriver/chrome/chrome_service_tests.py index 7fd1bd8193605..930b261ec5d82 100644 --- a/py/test/selenium/webdriver/chrome/chrome_service_tests.py +++ b/py/test/selenium/webdriver/chrome/chrome_service_tests.py @@ -18,12 +18,14 @@ import os import subprocess import sys +from pathlib import Path from unittest.mock import patch import pytest from selenium.common.exceptions import SessionNotCreatedException from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.driver_finder import DriverFinder @pytest.mark.no_driver_after_test @@ -142,6 +144,41 @@ def test_service_allows_reusing_stdout_for_logging(clean_driver, clean_options, browser2.quit() +def _is_within_cache(path: Path, cache_dir: Path) -> bool: + """Check if a path is within a given cache directory.""" + try: + path.relative_to(cache_dir) + return True + except ValueError: + return False + + +@pytest.mark.skipif( + not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"), + reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set", +) +def test_selenium_manager_resolves_browser_and_driver(clean_options) -> None: + """Verify Selenium Manager resolves both driver and browser via DriverFinder. + + These paths should point to executable files downloaded into the SM cache. + """ + cache_dir = Path(os.environ.get("SE_CACHE_PATH", Path.home() / ".cache" / "selenium")) + service = Service() + driver_finder = DriverFinder(service, clean_options) + + driver_path = Path(driver_finder.get_driver_path()) + browser_path = Path(driver_finder.get_browser_path()) + + assert driver_path.is_file(), f"Driver not found: {driver_path}" + assert browser_path.is_file(), f"Browser not found: {browser_path}" + + assert os.access(str(driver_path), os.X_OK), f"Driver not executable: {driver_path}" + assert os.access(str(browser_path), os.X_OK), f"Browser not executable: {browser_path}" + + assert _is_within_cache(driver_path, cache_dir), f"Driver path outside cache: {driver_path}" + assert _is_within_cache(browser_path, cache_dir), f"Browser path outside cache: {browser_path}" + + @pytest.fixture def service(): return Service() diff --git a/py/test/selenium/webdriver/edge/edge_service_tests.py b/py/test/selenium/webdriver/edge/edge_service_tests.py index 8443bacafca4a..a8b230378add3 100644 --- a/py/test/selenium/webdriver/edge/edge_service_tests.py +++ b/py/test/selenium/webdriver/edge/edge_service_tests.py @@ -18,11 +18,13 @@ import os import subprocess import sys +from pathlib import Path from unittest.mock import patch import pytest from selenium.common.exceptions import SessionNotCreatedException +from selenium.webdriver.common.driver_finder import DriverFinder from selenium.webdriver.edge.service import Service @@ -142,6 +144,41 @@ def test_service_allows_reusing_stdout_for_logging(clean_driver, clean_options, browser2.quit() +def _is_within_cache(path: Path, cache_dir: Path) -> bool: + """Check if a path is within a given cache directory.""" + try: + path.relative_to(cache_dir) + return True + except ValueError: + return False + + +@pytest.mark.skipif( + not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"), + reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set", +) +def test_selenium_manager_resolves_browser_and_driver(clean_options) -> None: + """Verify Selenium Manager resolves both driver and browser via DriverFinder. + + These paths should point to executable files downloaded into the SM cache. + """ + cache_dir = Path(os.environ.get("SE_CACHE_PATH", Path.home() / ".cache" / "selenium")) + service = Service() + driver_finder = DriverFinder(service, clean_options) + + driver_path = Path(driver_finder.get_driver_path()) + browser_path = Path(driver_finder.get_browser_path()) + + assert driver_path.is_file(), f"Driver not found: {driver_path}" + assert browser_path.is_file(), f"Browser not found: {browser_path}" + + assert os.access(str(driver_path), os.X_OK), f"Driver not executable: {driver_path}" + assert os.access(str(browser_path), os.X_OK), f"Browser not executable: {browser_path}" + + assert _is_within_cache(driver_path, cache_dir), f"Driver path outside cache: {driver_path}" + assert _is_within_cache(browser_path, cache_dir), f"Browser path outside cache: {browser_path}" + + @pytest.fixture def service(): return Service() diff --git a/py/test/selenium/webdriver/firefox/firefox_service_tests.py b/py/test/selenium/webdriver/firefox/firefox_service_tests.py index 1c28feea45794..200bcbdb9e457 100644 --- a/py/test/selenium/webdriver/firefox/firefox_service_tests.py +++ b/py/test/selenium/webdriver/firefox/firefox_service_tests.py @@ -18,12 +18,14 @@ import os import subprocess import sys +from pathlib import Path from unittest.mock import patch import pytest from selenium.common.exceptions import SessionNotCreatedException from selenium.webdriver import Firefox +from selenium.webdriver.common.driver_finder import DriverFinder from selenium.webdriver.firefox.options import Options from selenium.webdriver.firefox.service import Service @@ -93,6 +95,41 @@ def test_service_allows_reusing_stdout_for_logging(clean_driver, clean_options, browser2.quit() +def _is_within_cache(path: Path, cache_dir: Path) -> bool: + """Check if a path is within a given cache directory.""" + try: + path.relative_to(cache_dir) + return True + except ValueError: + return False + + +@pytest.mark.skipif( + not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"), + reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set", +) +def test_selenium_manager_resolves_browser_and_driver(clean_options) -> None: + """Verify Selenium Manager resolves both driver and browser via DriverFinder. + + These paths should point to executable files downloaded into the SM cache. + """ + cache_dir = Path(os.environ.get("SE_CACHE_PATH", Path.home() / ".cache" / "selenium")) + service = Service() + driver_finder = DriverFinder(service, clean_options) + + driver_path = Path(driver_finder.get_driver_path()) + browser_path = Path(driver_finder.get_browser_path()) + + assert driver_path.is_file(), f"Driver not found: {driver_path}" + assert browser_path.is_file(), f"Browser not found: {browser_path}" + + assert os.access(str(driver_path), os.X_OK), f"Driver not executable: {driver_path}" + assert os.access(str(browser_path), os.X_OK), f"Browser not executable: {browser_path}" + + assert _is_within_cache(driver_path, cache_dir), f"Driver path outside cache: {driver_path}" + assert _is_within_cache(browser_path, cache_dir), f"Browser path outside cache: {browser_path}" + + @pytest.fixture def service(): return Service()