2727import pytest
2828import rich .console
2929import rich .traceback
30+ from python .runfiles import Runfiles
3031
3132from selenium import webdriver
3233from selenium .common .exceptions import WebDriverException
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
155161def 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
212215class 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" )
479497def 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
570601def 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
591622def 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