Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +64 to +72
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Bazel filter skips sm suites 🐞 Bug ≡ Correctness

The new CI job filters by --test_tag_filters="manager,...", but py_test_suite generates
native.test_suite(name="test-*-sm") targets without propagating tags, so these suite targets don’t
have the manager tag and can be filtered out (preventing the underlying SM pytest targets from
running). This can make the new CI job pass while executing none of the intended Selenium Manager
tests.
Agent Prompt
### Issue description
The CI job runs `bazel test` with `--test_tag_filters=manager`, but the intermediate `test-%s-sm` targets are `native.test_suite` rules created by `py_test_suite` without inheriting tags. This can cause Bazel’s tag filtering to drop those suite targets, so the job may run none of the intended Selenium Manager tests.

### Issue Context
- The job already targets `//py:test-sm`, so the positive tag filter is redundant.
- `py_test_suite` forwards `tags` to the underlying `pytest_test(...)` rules, but not to the `native.test_suite(...)` wrapper.

### Fix Focus Areas
Choose one:
- Simplest: remove the positive tag/size filters in the CI job and rely on explicit target selection.
- More robust: update `py/private/suite.bzl` so `native.test_suite(...)` receives `tags` (and any other relevant attrs) when `py_test_suite` is called.

- .github/workflows/ci-python.yml[64-72]
- py/private/suite.bzl[18-50]
- py/BUILD.bazel[815-858]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


browser-tests:
name: Browser Tests
needs: build
Expand Down
45 changes: 45 additions & 0 deletions py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,51 @@ BROWSER_TESTS = {
]
]

# Generate test-<browser>-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.
Comment on lines +816 to +817
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says these suites "use --pin_browsers=false", but that flag is only applied in the GitHub Actions job and isn’t enforced by the Bazel targets themselves. Consider rewording to indicate the suites are intended to be run with --pin_browsers=false (e.g., in CI) to avoid confusing local runs.

Suggested change
# These only run when SE_FORCE_BROWSER_DOWNLOAD=true is set and use
# --pin_browsers=false to test SM download resolution.
# These only run when SE_FORCE_BROWSER_DOWNLOAD=true is set and are
# intended to be run with --pin_browsers=false (for example in CI) to
# test SM download resolution.

Copilot uses AI. Check for mistakes.
[
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",
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Selenium Manager integration tests rely on network access and on running with --pin_browsers=false, but the repo’s remote Bazel config forces --//common:pin_browsers and only filters out tests tagged skip-rbe. Without a skip-rbe tag here, running this suite under --config=rbe is likely to fail (and may attempt unwanted downloads). Add skip-rbe to the tags for these test-*-sm targets to prevent accidental RBE execution.

Suggested change
"requires-network",
"requires-network",
"skip-rbe",

Copilot uses AI. Check for mistakes.
],
Comment on lines +829 to +837
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. test-*-sm missing skip-rbe tag 📎 Requirement gap ☼ Reliability

The new Selenium Manager Bazel test suites are not tagged with skip-rbe, so they can execute under
Bazel RBE even though they require network/browser downloads. This violates the requirement to
ensure Selenium Manager-specific tests run on GitHub Actions but are skipped/disabled on RBE.
Agent Prompt
## Issue description
The new Selenium Manager Bazel test suites (`test-*-sm`) are eligible to run under RBE because they are missing the `skip-rbe` tag. These tests require network/browser downloads and should be restricted to GitHub Actions, not RBE.

## Issue Context
RBE runs tests with `--test_tag_filters=-skip-rbe` (so only targets explicitly tagged `skip-rbe` are excluded). The new suites currently have tags like `requires-network` but not `skip-rbe`.

## Fix Focus Areas
- py/BUILD.bazel[829-837]
- .bazelrc.remote[36-36]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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"],
Expand Down
37 changes: 37 additions & 0 deletions py/test/selenium/webdriver/chrome/chrome_service_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Comment on lines +149 to +153
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache path checks will be more robust if you normalize paths first (e.g., handle SE_CACHE_PATH values containing ~ and avoid ../symlink issues). Consider expanduser()/resolve() before doing the “within cache” assertion. Also, since Python >=3.10 is required, Path.is_relative_to() can replace this custom _is_within_cache helper and avoid the try/except.

Suggested change
try:
path.relative_to(cache_dir)
return True
except ValueError:
return False
normalized_path = path.expanduser().resolve()
normalized_cache_dir = cache_dir.expanduser().resolve()
return normalized_path.is_relative_to(normalized_cache_dir)

Copilot uses AI. Check for mistakes.


@pytest.mark.skipif(
not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"),
reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set",
Comment on lines +157 to +158
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SE_FORCE_BROWSER_DOWNLOAD is a boolean config key in Selenium Manager (parsed from env as "true"/"false"). The skip condition currently only checks for the variable being present, so SE_FORCE_BROWSER_DOWNLOAD=false would still run this test even though SM won't force downloads, making the cache assertions unreliable. Update the skipif condition to check the value (e.g., normalize and compare to "true").

Suggested change
not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"),
reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set",
os.environ.get("SE_FORCE_BROWSER_DOWNLOAD", "").strip().lower() != "true",
reason='Only runs when SE_FORCE_BROWSER_DOWNLOAD is set to "true"',

Copilot uses AI. Check for mistakes.
)
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()
Comment on lines +165 to +166
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Selenium Manager cache path is configured via SE_CACHE_PATH (and defaults to ~/.cache/selenium). This test reads SE_CACHE, so if CI/users override the cache location via SE_CACHE_PATH, the _is_within_cache assertions can fail even when Selenium Manager behaves correctly. Use SE_CACHE_PATH here for consistency with the other service tests and Manager configuration.

Copilot uses AI. Check for mistakes.
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()
Expand Down
37 changes: 37 additions & 0 deletions py/test/selenium/webdriver/edge/edge_service_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Comment on lines +149 to +153
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider normalizing paths (expanduser()/resolve()) before checking they’re under the cache directory; this avoids failures when SE_CACHE_PATH uses ~ or when SM returns paths containing ../symlinks. Since Python >=3.10 is required, Path.is_relative_to() can replace _is_within_cache and remove the try/except.

Suggested change
try:
path.relative_to(cache_dir)
return True
except ValueError:
return False
normalized_path = path.expanduser().resolve()
normalized_cache_dir = cache_dir.expanduser().resolve()
return normalized_path.is_relative_to(normalized_cache_dir)

Copilot uses AI. Check for mistakes.


@pytest.mark.skipif(
not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"),
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SE_FORCE_BROWSER_DOWNLOAD is parsed by Selenium Manager as a boolean ("true"/"false"). This skipif only checks whether the env var exists, so SE_FORCE_BROWSER_DOWNLOAD=false would still run the test even though SM won't force downloads. Update the condition to check the value (case-insensitive) instead of mere presence.

Suggested change
not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"),
os.environ.get("SE_FORCE_BROWSER_DOWNLOAD", "").lower() != "true",

Copilot uses AI. Check for mistakes.
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}"
Comment on lines +178 to +179
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, Selenium Manager installs Microsoft Edge via MSI into the system location rather than the SM cache (see rust/src/lib.rs where Edge-on-Windows is explicitly noted as using system path, not cache). This test unconditionally asserts browser_path is within the cache directory, which will fail on win32 even when SM is working as designed. Adjust the assertion to allow the system install path for Edge on Windows (or skip the browser-in-cache check on that platform) while still asserting the driver is in cache.

Copilot uses AI. Check for mistakes.


@pytest.fixture
def service():
return Service()
Expand Down
37 changes: 37 additions & 0 deletions py/test/selenium/webdriver/firefox/firefox_service_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Comment on lines +98 to +104
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make the cache containment assertion more robust, normalize paths first (e.g., expanduser()/resolve()), especially if SE_CACHE_PATH contains ~ or SM returns non-normalized paths. Since Python >=3.10 is required, Path.is_relative_to() can replace _is_within_cache and avoid the try/except.

Copilot uses AI. Check for mistakes.


@pytest.mark.skipif(
not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"),
reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set",
Comment on lines +108 to +109
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SE_FORCE_BROWSER_DOWNLOAD is a boolean Selenium Manager config env var (parsed as "true"/"false"). This skipif only checks for presence, so the test would still run with SE_FORCE_BROWSER_DOWNLOAD=false even though SM won’t force browser downloads, making the cache assertions unreliable. Update the condition to validate the value instead of just existence.

Suggested change
not os.environ.get("SE_FORCE_BROWSER_DOWNLOAD"),
reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set",
os.environ.get("SE_FORCE_BROWSER_DOWNLOAD", "").lower() != "true",
reason="Only runs when SE_FORCE_BROWSER_DOWNLOAD is set to true",

Copilot uses AI. Check for mistakes.
)
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()
Comment on lines +116 to +117
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Selenium Manager cache path env var is SE_CACHE_PATH (and defaults to ~/.cache/selenium). This test uses SE_CACHE, which can cause false failures when the cache is overridden via SE_CACHE_PATH. Update this to read SE_CACHE_PATH for consistency with Selenium Manager configuration.

Copilot uses AI. Check for mistakes.
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()
Expand Down
Loading