Skip to content

Commit b1904f8

Browse files
authored
[py] improve how tests are run against the grid server (#16851)
* [py] improve how tests are run against the grid server * put the test-remote job back as alias * use removesuffix
1 parent dfdea8c commit b1904f8

12 files changed

Lines changed: 205 additions & 102 deletions

File tree

py/BUILD.bazel

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ TEST_DEPS = [
8282
"@rules_python//python/runfiles",
8383
]
8484

85+
genrule(
86+
name = "java-location",
87+
srcs = [],
88+
outs = ["java-location.txt"],
89+
cmd = "echo $(JAVA) > $@",
90+
toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"],
91+
)
92+
8593
copy_file(
8694
name = "manager-linux",
8795
src = "//common/manager:selenium-manager-linux",
@@ -502,33 +510,57 @@ BROWSER_TESTS = {
502510
if BROWSER_TESTS[browser].get("bidi", False)
503511
]
504512

505-
py_test_suite(
506-
name = "test-remote",
507-
size = "large",
508-
srcs = glob(
509-
[
510-
"test/selenium/webdriver/common/**/*.py",
511-
"test/selenium/webdriver/remote/**/*.py",
512-
"test/selenium/webdriver/support/**/*.py",
513+
# Generate test-<browser>-remote targets (chrome and firefox only)
514+
[
515+
py_test_suite(
516+
name = "test-%s-remote" % browser,
517+
size = "large",
518+
srcs = glob(
519+
[
520+
"test/selenium/webdriver/common/**/*.py",
521+
"test/selenium/webdriver/remote/**/*.py",
522+
"test/selenium/webdriver/support/**/*.py",
523+
] + BROWSER_TESTS[browser]["browser_srcs"],
524+
exclude = BIDI_TESTS + ["test/selenium/webdriver/common/print_pdf_tests.py"] +
525+
BROWSER_TESTS[browser].get("extra_excludes", []),
526+
),
527+
args = [
528+
"--instafail",
529+
"--remote",
530+
] + BROWSERS[browser]["args"],
531+
data = BROWSERS[browser]["data"] + [
532+
":java-location",
533+
"//java/src/org/openqa/selenium/grid:selenium_server_deploy.jar",
534+
"@bazel_tools//tools/jdk:current_java_runtime",
513535
],
514-
exclude = BIDI_TESTS,
515-
),
516-
args = [
517-
"--instafail",
518-
"--driver=remote",
519-
],
520-
data = [
521-
"//java/src/org/openqa/selenium/grid:selenium_server_deploy.jar",
522-
],
523-
tags = [
524-
"no-sandbox",
525-
"skip-rbe",
536+
env = {
537+
"SE_BAZEL_JAVA_LOCATION": "$(rootpath :java-location)",
538+
},
539+
env_inherit = ["DISPLAY"],
540+
tags = [
541+
"no-sandbox",
542+
"remote",
543+
"%s-remote" % browser,
544+
],
545+
deps = [
546+
":init-tree",
547+
":selenium",
548+
":webserver",
549+
] + TEST_DEPS,
550+
)
551+
for browser in [
552+
"chrome",
553+
"firefox",
554+
]
555+
]
556+
557+
test_suite(
558+
name = "test-remote",
559+
tags = ["remote"],
560+
tests = [
561+
":test-chrome-remote",
562+
":test-firefox-remote",
526563
],
527-
deps = [
528-
":init-tree",
529-
":selenium",
530-
":webserver",
531-
] + TEST_DEPS,
532564
)
533565

534566
py_test_suite(

py/conftest.py

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import pytest
2828
import rich.console
2929
import rich.traceback
30+
from python.runfiles import Runfiles
3031

3132
from selenium import webdriver
3233
from selenium.common.exceptions import WebDriverException
@@ -40,7 +41,6 @@
4041
"edge",
4142
"firefox",
4243
"ie",
43-
"remote",
4444
"safari",
4545
"webkitgtk",
4646
"wpewebkit",
@@ -150,6 +150,12 @@ def pytest_addoption(parser):
150150
dest="bidi",
151151
help="Enable BiDi support",
152152
)
153+
parser.addoption(
154+
"--remote",
155+
action="store_true",
156+
dest="remote",
157+
help="Run tests against a remote Grid server",
158+
)
153159

154160

155161
def pytest_ignore_collect(collection_path, config):
@@ -186,7 +192,6 @@ class SupportedDrivers(ContainerProtocol):
186192
ie: str = "Ie"
187193
webkitgtk: str = "WebKitGTK"
188194
wpewebkit: str = "WPEWebKit"
189-
remote: str = "Remote"
190195

191196

192197
@dataclass
@@ -196,7 +201,6 @@ class SupportedOptions(ContainerProtocol):
196201
edge: str = "EdgeOptions"
197202
safari: str = "SafariOptions"
198203
ie: str = "IeOptions"
199-
remote: str = "ChromeOptions"
200204
webkitgtk: str = "WebKitGTKOptions"
201205
wpewebkit: str = "WPEWebKitOptions"
202206

@@ -206,7 +210,6 @@ class SupportedBidiDrivers(ContainerProtocol):
206210
chrome: str = "Chrome"
207211
firefox: str = "Firefox"
208212
edge: str = "Edge"
209-
remote: str = "Remote"
210213

211214

212215
class Driver:
@@ -215,6 +218,7 @@ def __init__(self, driver_class, request):
215218
self._request = request
216219
self._driver = None
217220
self._service = None
221+
self._server = None
218222
self.options = driver_class
219223
self.headless = driver_class
220224
self.bidi = driver_class
@@ -313,17 +317,16 @@ def options(self, cls_name):
313317
# There are issues with window size/position when running Firefox
314318
# under Wayland, so we use XWayland instead.
315319
os.environ["MOZ_ENABLE_WAYLAND"] = "0"
316-
elif self.driver_class == self.supported_drivers.remote:
317-
self._options = getattr(webdriver, self.supported_options.chrome)()
318-
self._options.set_capability("goog:chromeOptions", {})
319-
self._options.enable_downloads = True
320320
else:
321321
opts_cls = getattr(self.supported_options, cls_name.lower())
322322
self._options = getattr(webdriver, opts_cls)()
323323

324324
if cls_name.lower() in ("chrome", "edge"):
325325
self._options.add_argument("--disable-dev-shm-usage")
326326

327+
if self.is_remote:
328+
self._options.enable_downloads = True
329+
327330
if self.browser_path or self.browser_args:
328331
if self.driver_class == self.supported_drivers.webkitgtk:
329332
self._options.overlay_scrollbars_enabled = False
@@ -358,10 +361,17 @@ def is_platform_valid(self):
358361
return False
359362
return True
360363

364+
@property
365+
def is_remote(self):
366+
return self._request.config.getoption("remote")
367+
361368
def _initialize_driver(self):
362369
kwargs = {}
363370
if self.options is not None:
364371
kwargs["options"] = self.options
372+
if self.is_remote:
373+
kwargs["command_executor"] = self._server.status_url.removesuffix("/status")
374+
return webdriver.Remote(**kwargs)
365375
if self.driver_path is not None:
366376
kwargs["service"] = self.service
367377
return getattr(webdriver, self.driver_class)(**kwargs)
@@ -374,20 +384,22 @@ def stop_driver(self):
374384

375385

376386
@pytest.fixture
377-
def driver(request):
387+
def driver(request, server):
378388
global selenium_driver
379389
driver_class = getattr(request, "param", "Chrome").lower()
380390

381391
if selenium_driver is None:
382392
selenium_driver = Driver(driver_class, request)
393+
if server:
394+
selenium_driver._server = server
383395

384396
# skip tests if not available on the platform
385397
if not selenium_driver.is_platform_valid:
386398
pytest.skip(f"{driver_class} tests can only run on {selenium_driver.exe_platform}")
387399

388-
# skip tests in the 'remote' directory if run with a local driver
389-
if request.node.path.parts[-2] == "remote" and selenium_driver.driver_class != "Remote":
390-
pytest.skip(f"Remote tests can't be run with driver '{selenium_driver.driver_class}'")
400+
# skip tests in the 'remote' directory if not running with --remote flag
401+
if request.node.path.parts[-2] == "remote" and not selenium_driver.is_remote:
402+
pytest.skip("Remote tests require the --remote flag")
391403

392404
# skip tests for drivers that don't support BiDi when --bidi is enabled
393405
if selenium_driver.bidi:
@@ -396,17 +408,23 @@ def driver(request):
396408

397409
# conditionally mark tests as expected to fail based on driver
398410
marker = request.node.get_closest_marker(f"xfail_{driver_class.lower()}")
411+
# Also check for xfail_remote when running with --remote
412+
if marker is None and selenium_driver.is_remote:
413+
marker = request.node.get_closest_marker("xfail_remote")
399414
if marker is not None:
400-
if "run" in marker.kwargs:
401-
if not marker.kwargs["run"]:
402-
pytest.skip()
403-
yield
404-
return
405-
if "raises" in marker.kwargs:
406-
marker.kwargs.pop("raises")
407-
pytest.xfail(**marker.kwargs)
408-
409-
request.addfinalizer(selenium_driver.stop_driver)
415+
kwargs = dict(marker.kwargs)
416+
# Support condition kwarg - if condition is False, skip the xfail
417+
condition = kwargs.pop("condition", True)
418+
if callable(condition):
419+
condition = condition()
420+
if condition:
421+
if "run" in kwargs:
422+
if not kwargs["run"]:
423+
pytest.skip()
424+
yield
425+
return
426+
kwargs.pop("raises", None)
427+
pytest.xfail(**kwargs)
410428

411429
# For BiDi tests, only restart driver when explicitly marked as needing fresh driver.
412430
# Tests marked with @pytest.mark.needs_fresh_driver get full driver restart for test isolation.
@@ -477,28 +495,38 @@ def load(self, name):
477495

478496
@pytest.fixture(autouse=True, scope="session")
479497
def server(request):
480-
drivers = request.config.getoption("drivers")
481-
if drivers is None or "remote" not in drivers:
498+
is_remote = request.config.getoption("remote")
499+
if not is_remote:
482500
yield None
483501
return
484502

485-
jar_path = os.path.join(
486-
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
487-
"java/src/org/openqa/selenium/grid/selenium_server_deploy.jar",
488-
)
503+
r = Runfiles.Create()
504+
505+
java_location_txt = r.Rlocation("_main/" + os.environ.get("SE_BAZEL_JAVA_LOCATION"))
506+
try:
507+
with open(java_location_txt, encoding="utf-8") as handle:
508+
read = handle.read().strip()
509+
rel_path = read[len("external/") :] if read.startswith("external/") else read
510+
java_path = r.Rlocation(rel_path)
511+
except Exception:
512+
java_path = None
513+
514+
built_jar = "selenium/java/src/org/openqa/selenium/grid/selenium_server_deploy.jar"
515+
jar_path = r.Rlocation(built_jar)
489516

490517
remote_env = os.environ.copy()
491518
if sys.platform == "linux":
492519
# There are issues with window size/position when running Firefox
493520
# under Wayland, so we use XWayland instead.
494521
remote_env["MOZ_ENABLE_WAYLAND"] = "0"
495522

523+
server = Server(env=remote_env, startup_timeout=60)
524+
if Path(java_path).exists():
525+
server.java_path = java_path
496526
if Path(jar_path).exists():
497-
# use the grid server built by bazel
498-
server = Server(path=jar_path, env=remote_env)
499-
else:
500-
# use the local grid server (downloads a new one if needed)
501-
server = Server(env=remote_env)
527+
server.path = jar_path
528+
529+
server.port = free_port()
502530
server.start()
503531
yield server
504532
server.stop()
@@ -537,15 +565,18 @@ def clean_driver(request):
537565

538566
# conditionally mark tests as expected to fail based on driver
539567
marker = request.node.get_closest_marker(f"xfail_{driver_class.lower()}")
568+
# Also check for xfail_remote when running with --remote
569+
if marker is None and request.config.getoption("remote"):
570+
marker = request.node.get_closest_marker("xfail_remote")
540571
if marker is not None:
541-
if "run" in marker.kwargs:
542-
if not marker.kwargs["run"]:
572+
kwargs = dict(marker.kwargs)
573+
if "run" in kwargs:
574+
if not kwargs["run"]:
543575
pytest.skip()
544576
yield
545577
return
546-
if "raises" in marker.kwargs:
547-
marker.kwargs.pop("raises")
548-
pytest.xfail(**marker.kwargs)
578+
kwargs.pop("raises", None)
579+
pytest.xfail(**kwargs)
549580

550581
yield driver_reference
551582

@@ -568,19 +599,19 @@ def clean_options(request):
568599

569600
@pytest.fixture
570601
def firefox_options(request):
571-
_supported_drivers = SupportedDrivers()
572602
try:
573603
driver_class = request.config.option.drivers[0].lower()
574604
except (AttributeError, TypeError):
575605
raise Exception("This test requires a --driver to be specified")
576606

577-
# skip if not Firefox or Remote
578-
if driver_class not in ("firefox", "remote"):
579-
pytest.skip(f"This test requires Firefox or Remote. Got {driver_class}")
607+
# skip if not Firefox
608+
if driver_class != "firefox":
609+
pytest.skip(f"This test requires Firefox. Got {driver_class}")
580610

581-
# skip tests in the 'remote' directory if run with a local driver
582-
if request.node.path.parts[-2] == "remote" and getattr(_supported_drivers, driver_class) != "Remote":
583-
pytest.skip(f"Remote tests can't be run with driver '{driver_class}'")
611+
# skip tests in the 'remote' directory if not running with --remote flag
612+
is_remote = request.config.getoption("remote")
613+
if request.node.path.parts[-2] == "remote" and not is_remote:
614+
pytest.skip("Remote tests require the --remote flag")
584615

585616
options = Driver.clean_options("firefox", request)
586617

@@ -589,24 +620,21 @@ def firefox_options(request):
589620

590621
@pytest.fixture
591622
def chromium_options(request):
592-
_supported_drivers = SupportedDrivers()
593623
try:
594624
driver_class = request.config.option.drivers[0].lower()
595625
except (AttributeError, TypeError):
596626
raise Exception("This test requires a --driver to be specified")
597627

598-
# skip if not Chrome, Edge, or Remote
599-
if driver_class not in ("chrome", "edge", "remote"):
600-
pytest.skip(f"This test requires Chrome, Edge, or Remote. Got {driver_class}")
628+
# skip if not Chrome or Edge
629+
if driver_class not in ("chrome", "edge"):
630+
pytest.skip(f"This test requires Chrome or Edge. Got {driver_class}")
601631

602-
# skip tests in the 'remote' directory if run with a local driver
603-
if request.node.path.parts[-2] == "remote" and getattr(_supported_drivers, driver_class) != "Remote":
604-
pytest.skip(f"Remote tests can't be run with driver '{driver_class}'")
632+
# skip tests in the 'remote' directory if not running with --remote flag
633+
is_remote = request.config.getoption("remote")
634+
if request.node.path.parts[-2] == "remote" and not is_remote:
635+
pytest.skip("Remote tests require the --remote flag")
605636

606-
if driver_class in ("chrome", "remote"):
607-
options = Driver.clean_options("chrome", request)
608-
else:
609-
options = Driver.clean_options("edge", request)
637+
options = Driver.clean_options(driver_class, request)
610638

611639
return options
612640

0 commit comments

Comments
 (0)