seleniumbase 4.44.2__py3-none-any.whl → 4.45.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. seleniumbase/__version__.py +1 -1
  2. seleniumbase/behave/behave_sb.py +14 -0
  3. seleniumbase/console_scripts/run.py +1 -0
  4. seleniumbase/console_scripts/sb_install.py +142 -11
  5. seleniumbase/console_scripts/sb_mkdir.py +76 -0
  6. seleniumbase/console_scripts/sb_mkrec.py +25 -0
  7. seleniumbase/console_scripts/sb_recorder.py +40 -3
  8. seleniumbase/core/browser_launcher.py +279 -117
  9. seleniumbase/core/detect_b_ver.py +8 -6
  10. seleniumbase/core/log_helper.py +11 -16
  11. seleniumbase/core/mysql.py +1 -1
  12. seleniumbase/core/report_helper.py +3 -7
  13. seleniumbase/core/sb_cdp.py +275 -78
  14. seleniumbase/core/sb_driver.py +36 -5
  15. seleniumbase/core/session_helper.py +2 -4
  16. seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
  17. seleniumbase/fixtures/base_case.py +251 -201
  18. seleniumbase/fixtures/constants.py +1 -0
  19. seleniumbase/fixtures/js_utils.py +52 -14
  20. seleniumbase/fixtures/page_actions.py +18 -7
  21. seleniumbase/fixtures/page_utils.py +4 -2
  22. seleniumbase/fixtures/shared_utils.py +2 -4
  23. seleniumbase/masterqa/master_qa.py +16 -2
  24. seleniumbase/plugins/base_plugin.py +8 -0
  25. seleniumbase/plugins/driver_manager.py +15 -5
  26. seleniumbase/plugins/pytest_plugin.py +43 -57
  27. seleniumbase/plugins/sb_manager.py +23 -19
  28. seleniumbase/plugins/selenium_plugin.py +20 -13
  29. seleniumbase/undetected/__init__.py +11 -10
  30. seleniumbase/undetected/cdp.py +1 -12
  31. seleniumbase/undetected/cdp_driver/browser.py +330 -128
  32. seleniumbase/undetected/cdp_driver/cdp_util.py +48 -14
  33. seleniumbase/undetected/cdp_driver/config.py +78 -11
  34. seleniumbase/undetected/cdp_driver/connection.py +15 -43
  35. seleniumbase/undetected/cdp_driver/element.py +37 -22
  36. seleniumbase/undetected/cdp_driver/tab.py +414 -39
  37. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/METADATA +140 -152
  38. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/RECORD +42 -41
  39. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/licenses/LICENSE +1 -1
  40. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/WHEEL +0 -0
  41. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/entry_points.txt +0 -0
  42. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/top_level.txt +0 -0
@@ -446,6 +446,7 @@ class ValidBinaries:
446
446
  "Opera",
447
447
  "Comet Browser",
448
448
  "Comet",
449
+ "ChatGPT Atlas",
449
450
  "Atlas Browser",
450
451
  "Atlas",
451
452
  ]
@@ -31,7 +31,7 @@ def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT):
31
31
  (Previously, tests would fail immediately if exceeding the timeout.)"""
32
32
  if hasattr(driver, "_swap_driver"):
33
33
  return
34
- if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS:
34
+ if getattr(settings, "SKIP_JS_WAITS", None):
35
35
  return
36
36
  start_ms = time.time() * 1000.0
37
37
  stop_ms = start_ms + (timeout * 1000.0)
@@ -65,13 +65,13 @@ def execute_async_script(driver, script, timeout=settings.LARGE_TIMEOUT):
65
65
 
66
66
 
67
67
  def wait_for_angularjs(driver, timeout=settings.LARGE_TIMEOUT, **kwargs):
68
- if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS:
68
+ if getattr(settings, "SKIP_JS_WAITS", None):
69
69
  return
70
70
  with suppress(Exception):
71
71
  # This closes pop-up alerts
72
72
  execute_script(driver, "")
73
73
  if (
74
- (hasattr(driver, "_is_using_uc") and driver._is_using_uc)
74
+ getattr(driver, "_is_using_uc", None)
75
75
  or not settings.WAIT_FOR_ANGULARJS
76
76
  ):
77
77
  wait_for_ready_state_complete(driver)
@@ -211,6 +211,19 @@ def activate_jquery(driver):
211
211
  try:
212
212
  execute_script(driver, "jQuery('html');")
213
213
  return
214
+ except TypeError as e:
215
+ if (
216
+ (
217
+ shared_utils.is_cdp_swap_needed(driver)
218
+ or hasattr(driver, "_swap_driver")
219
+ )
220
+ and "cannot unpack non-iterable" in str(e)
221
+ ):
222
+ pass
223
+ else:
224
+ if x == 18:
225
+ add_js_link(driver, jquery_js)
226
+ time.sleep(0.1)
214
227
  except Exception:
215
228
  if x == 18:
216
229
  add_js_link(driver, jquery_js)
@@ -282,6 +295,18 @@ def safe_execute_script(driver, script):
282
295
  This method will load jQuery if it wasn't already loaded."""
283
296
  try:
284
297
  execute_script(driver, script)
298
+ except TypeError as e:
299
+ if (
300
+ (
301
+ shared_utils.is_cdp_swap_needed(driver)
302
+ or hasattr(driver, "_swap_driver")
303
+ )
304
+ and "cannot unpack non-iterable" in str(e)
305
+ ):
306
+ pass
307
+ else:
308
+ activate_jquery(driver) # It's a good thing we can define it here
309
+ execute_script(driver, script)
285
310
  except Exception:
286
311
  # The likely reason this fails is because: "jQuery is not defined"
287
312
  activate_jquery(driver) # It's a good thing we can define it here
@@ -874,7 +899,7 @@ def set_messenger_theme(
874
899
  theme = "future"
875
900
  if location == "default":
876
901
  location = "bottom_right"
877
- if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator:
902
+ if getattr(sb_config, "mobile_emulator", None):
878
903
  location = "top_center"
879
904
  if max_messages == "default":
880
905
  max_messages = "8"
@@ -914,15 +939,16 @@ def set_messenger_theme(
914
939
  % (max_messages, messenger_location, theme)
915
940
  )
916
941
  try:
942
+ time.sleep(0.015)
917
943
  execute_script(driver, msg_style)
918
944
  except Exception:
919
- time.sleep(0.03)
945
+ time.sleep(0.035)
920
946
  activate_messenger(driver)
921
- time.sleep(0.15)
947
+ time.sleep(0.175)
922
948
  with suppress(Exception):
923
949
  execute_script(driver, msg_style)
924
- time.sleep(0.02)
925
- time.sleep(0.05)
950
+ time.sleep(0.035)
951
+ time.sleep(0.055)
926
952
 
927
953
 
928
954
  def post_message(driver, message, msg_dur=None, style="info"):
@@ -977,7 +1003,7 @@ def post_messenger_success_message(driver, message, msg_dur=None):
977
1003
  with suppress(Exception):
978
1004
  theme = "future"
979
1005
  location = "bottom_right"
980
- if hasattr(sb_config, "mobile_emulator") and sb_config.mobile_emulator:
1006
+ if getattr(sb_config, "mobile_emulator", None):
981
1007
  location = "top_right"
982
1008
  set_messenger_theme(driver, theme=theme, location=location)
983
1009
  post_message(driver, message, msg_dur, style="success")
@@ -1310,22 +1336,34 @@ def slow_scroll_to_element(driver, element, *args, **kwargs):
1310
1336
  element_location_y = None
1311
1337
  try:
1312
1338
  if shared_utils.is_cdp_swap_needed(driver):
1313
- element.get_position().y
1339
+ element_location_y = element.get_position().y
1314
1340
  else:
1315
1341
  element_location_y = element.location["y"]
1316
1342
  except Exception:
1317
- element.location_once_scrolled_into_view
1343
+ if shared_utils.is_cdp_swap_needed(driver):
1344
+ element.scroll_into_view()
1345
+ else:
1346
+ element.location_once_scrolled_into_view
1318
1347
  return
1319
1348
  try:
1320
- element_location_x = element.location["x"]
1349
+ if shared_utils.is_cdp_swap_needed(driver):
1350
+ element_location_x = element.get_position().x
1351
+ else:
1352
+ element_location_x = element.location["x"]
1321
1353
  except Exception:
1322
1354
  element_location_x = 0
1323
1355
  try:
1324
- element_width = element.size["width"]
1356
+ if shared_utils.is_cdp_swap_needed(driver):
1357
+ element_width = element.get_position().width
1358
+ else:
1359
+ element_width = element.size["width"]
1325
1360
  except Exception:
1326
1361
  element_width = 0
1327
1362
  try:
1328
- screen_width = driver.get_window_size()["width"]
1363
+ if shared_utils.is_cdp_swap_needed(driver):
1364
+ screen_width = driver.cdp.get_window_size()["width"]
1365
+ else:
1366
+ screen_width = driver.get_window_size()["width"]
1329
1367
  except Exception:
1330
1368
  screen_width = execute_script(driver, "return window.innerWidth;")
1331
1369
  element_location_y = element_location_y - constants.Scroll.Y_OFFSET
@@ -1646,11 +1646,7 @@ def switch_to_frame(
1646
1646
 
1647
1647
 
1648
1648
  def __switch_to_window(driver, window_handle, uc_lock=True):
1649
- if (
1650
- hasattr(driver, "_is_using_uc")
1651
- and driver._is_using_uc
1652
- and uc_lock
1653
- ):
1649
+ if getattr(driver, "_is_using_uc", None) and uc_lock:
1654
1650
  gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
1655
1651
  with gui_lock:
1656
1652
  driver.switch_to.window(window_handle)
@@ -1734,8 +1730,7 @@ def switch_to_window(
1734
1730
 
1735
1731
  def _reconnect_if_disconnected(driver):
1736
1732
  if (
1737
- hasattr(driver, "_is_using_uc")
1738
- and driver._is_using_uc
1733
+ getattr(driver, "_is_using_uc", None)
1739
1734
  and hasattr(driver, "is_connected")
1740
1735
  and not driver.is_connected()
1741
1736
  ):
@@ -1756,6 +1751,22 @@ def open_url(driver, url):
1756
1751
  if __is_cdp_swap_needed(driver):
1757
1752
  driver.cdp.open(url)
1758
1753
  return
1754
+ elif (
1755
+ getattr(driver, "_is_using_uc", None)
1756
+ # and getattr(driver, "_is_using_auth", None)
1757
+ and not getattr(driver, "_is_using_cdp", None)
1758
+ ):
1759
+ # Auth in UC Mode requires CDP Mode
1760
+ # (and now we're always forcing it)
1761
+ driver.uc_activate_cdp_mode(url)
1762
+ return
1763
+ elif (
1764
+ getattr(driver, "_is_using_uc", None)
1765
+ and getattr(driver, "_is_using_cdp", None)
1766
+ ):
1767
+ driver.disconnect()
1768
+ driver.cdp.open(url)
1769
+ return
1759
1770
  url = str(url).strip() # Remove leading and trailing whitespace
1760
1771
  if not page_utils.looks_like_a_page_url(url):
1761
1772
  if page_utils.is_valid_url("https://" + url):
@@ -293,12 +293,14 @@ def _print_unique_links_with_status_codes(page_url, soup):
293
293
  print(link, " -> ", status_code)
294
294
 
295
295
 
296
- def _download_file_to(file_url, destination_folder, new_file_name=None):
296
+ def _download_file_to(
297
+ file_url, destination_folder, new_file_name=None, headers=None
298
+ ):
297
299
  if new_file_name:
298
300
  file_name = new_file_name
299
301
  else:
300
302
  file_name = file_url.split("/")[-1]
301
- r = requests.get(file_url, timeout=5)
303
+ r = requests.get(file_url, headers=headers, timeout=5)
302
304
  file_path = os.path.join(destination_folder, file_name)
303
305
  download_file_lock = fasteners.InterProcessLock(
304
306
  constants.MultiBrowser.DOWNLOAD_FILE_LOCK
@@ -147,8 +147,7 @@ def fix_url_as_needed(url):
147
147
 
148
148
  def reconnect_if_disconnected(driver):
149
149
  if (
150
- hasattr(driver, "_is_using_uc")
151
- and driver._is_using_uc
150
+ getattr(driver, "_is_using_uc", None)
152
151
  and hasattr(driver, "is_connected")
153
152
  and not driver.is_connected()
154
153
  ):
@@ -298,8 +297,7 @@ def __time_limit_exceeded(message):
298
297
 
299
298
  def check_if_time_limit_exceeded():
300
299
  if (
301
- hasattr(sb_config, "time_limit")
302
- and sb_config.time_limit
300
+ getattr(sb_config, "time_limit", None)
303
301
  and not sb_config.recorder_mode
304
302
  ):
305
303
  time_limit = sb_config.time_limit
@@ -11,6 +11,11 @@ from seleniumbase.config import settings
11
11
  from seleniumbase.fixtures import js_utils
12
12
 
13
13
 
14
+ python3_11_or_newer = False
15
+ if sys.version_info >= (3, 11):
16
+ python3_11_or_newer = True
17
+
18
+
14
19
  class MasterQA(BaseCase):
15
20
  def setUp(self):
16
21
  self.check_count = 0
@@ -309,8 +314,17 @@ class MasterQA(BaseCase):
309
314
  if hasattr(sys, "last_traceback") and sys.last_traceback is not None:
310
315
  has_exception = True
311
316
  elif hasattr(self, "_outcome"):
312
- if hasattr(self._outcome, "errors") and self._outcome.errors:
313
- has_exception = True
317
+ if hasattr(self._outcome, "errors"):
318
+ if python3_11_or_newer:
319
+ if (
320
+ self._outcome.errors
321
+ and self._outcome.errors[-1]
322
+ and self._outcome.errors[-1][1]
323
+ ):
324
+ has_exception = True
325
+ else:
326
+ if self._outcome.errors:
327
+ has_exception = True
314
328
  else:
315
329
  has_exception = sys.exc_info()[1] is not None
316
330
  return has_exception
@@ -240,14 +240,22 @@ class Base(Plugin):
240
240
  test.test.test_id = test.id()
241
241
  test.test.is_nosetest = True
242
242
  test.test.environment = self.options.environment
243
+ sb_config.environment = self.options.environment
243
244
  test.test.env = self.options.environment # Add a shortened version
244
245
  test.test.account = self.options.account
246
+ sb_config.account = self.options.account
245
247
  test.test.data = self.options.data
248
+ sb_config.data = self.options.data
246
249
  test.test.var1 = self.options.var1
250
+ sb_config.var1 = self.options.var1
247
251
  test.test.var2 = self.options.var2
252
+ sb_config.var2 = self.options.var2
248
253
  test.test.var3 = self.options.var3
254
+ sb_config.var3 = self.options.var3
249
255
  test.test.variables = variables # Already verified is a dictionary
256
+ sb_config.variables = variables
250
257
  test.test.settings_file = self.options.settings_file
258
+ sb_config.settings_file = self.options.settings_file
251
259
  test.test._final_debug = self.options.final_debug
252
260
  test.test.log_path = self.options.log_path
253
261
  if self.options.archive_downloads:
@@ -140,6 +140,7 @@ def Driver(
140
140
  pls=None, # Shortcut / Duplicate of "page_load_strategy".
141
141
  cft=None, # Use "Chrome for Testing"
142
142
  chs=None, # Use "Chrome-Headless-Shell"
143
+ use_chromium=None, # Use base "Chromium"
143
144
  ) -> sb_driver.DriverMethods:
144
145
  """
145
146
  * SeleniumBase Driver as a Python Context Manager or a returnable object. *
@@ -665,11 +666,15 @@ def Driver(
665
666
  if arg.startswith("--bl="):
666
667
  binary_location = arg.split("--bl=")[1]
667
668
  break
668
- if cft and not binary_location:
669
+ if use_chromium and not binary_location:
670
+ binary_location = "_chromium_"
671
+ elif cft and not binary_location:
669
672
  binary_location = "cft"
670
673
  elif chs and not binary_location:
671
674
  binary_location = "chs"
672
- if "--cft" in sys_argv and not binary_location:
675
+ if "--use-chromium" in sys_argv and not binary_location:
676
+ binary_location = "_chromium_"
677
+ elif "--cft" in sys_argv and not binary_location:
673
678
  binary_location = "cft"
674
679
  elif "--chs" in sys_argv and not binary_location:
675
680
  binary_location = "chs"
@@ -742,9 +747,12 @@ def Driver(
742
747
  uc_cdp_events = True
743
748
  else:
744
749
  uc_cdp_events = False
745
- if undetectable and browser != "chrome":
750
+ if (
751
+ undetectable
752
+ and browser not in ["chrome", "opera", "brave", "comet", "atlas"]
753
+ ):
746
754
  message = (
747
- '\n Undetected-Chromedriver Mode ONLY supports Chrome!'
755
+ '\n Undetected-Chromedriver Mode ONLY supports Chromium browsers!'
748
756
  '\n ("uc=True" / "undetectable=True" / "--uc")'
749
757
  '\n (Your browser choice was: "%s".)'
750
758
  '\n (Will use "%s" without UC Mode.)\n' % (browser, browser)
@@ -775,7 +783,9 @@ def Driver(
775
783
  if headless2 and browser == "firefox":
776
784
  headless2 = False # Only for Chromium browsers
777
785
  headless = True # Firefox has regular headless
778
- elif browser not in ["chrome", "edge"]:
786
+ elif browser not in [
787
+ "chrome", "edge", "opera", "brave", "comet", "atlas"
788
+ ]:
779
789
  headless2 = False # Only for Chromium browsers
780
790
  if disable_csp is None:
781
791
  if (
@@ -33,6 +33,7 @@ def pytest_addoption(parser):
33
33
  --brave (Shortcut for "--browser=brave".)
34
34
  --comet (Shortcut for "--browser=comet".)
35
35
  --atlas (Shortcut for "--browser=atlas".)
36
+ --use-chromium (Shortcut for using base `Chromium`)
36
37
  --cft (Shortcut for using `Chrome for Testing`)
37
38
  --chs (Shortcut for using `Chrome-Headless-Shell`)
38
39
  --settings-file=FILE (Override default SeleniumBase settings.)
@@ -219,6 +220,13 @@ def pytest_addoption(parser):
219
220
  default=False,
220
221
  help="""Shortcut for --browser=atlas""",
221
222
  )
223
+ parser.addoption(
224
+ "--use-chromium",
225
+ action="store_true",
226
+ dest="use_chromium",
227
+ default=False,
228
+ help="""Shortcut for using base `Chromium`""",
229
+ )
222
230
  parser.addoption(
223
231
  "--cft",
224
232
  action="store_true",
@@ -625,7 +633,7 @@ def pytest_addoption(parser):
625
633
  help="""Designates the three device metrics of the mobile
626
634
  emulator: CSS Width, CSS Height, and Pixel-Ratio.
627
635
  Format: A comma-separated string with the 3 values.
628
- Examples: "375,734,5" or "411,731,3" or "390,715,3"
636
+ Examples: "375,734,5" or "412,732,3" or "390,715,3"
629
637
  Default: None. (Will use default values if None)""",
630
638
  )
631
639
  parser.addoption(
@@ -1622,10 +1630,12 @@ def pytest_addoption(parser):
1622
1630
  if (
1623
1631
  using_recorder
1624
1632
  and browser_changes == 1
1625
- and browser_text not in ["chrome", "edge"]
1633
+ and browser_text not in [
1634
+ "chrome", "edge", "opera", "brave", "comet", "atlas", "chromium"
1635
+ ]
1626
1636
  ):
1627
1637
  message = (
1628
- "\n Recorder Mode ONLY supports Chrome and Edge!"
1638
+ "\n Recorder Mode ONLY supports Chromium browsers!"
1629
1639
  '\n (Your browser choice was: "%s")\n' % browser_list[0]
1630
1640
  )
1631
1641
  raise Exception(message)
@@ -1644,7 +1654,9 @@ def pytest_addoption(parser):
1644
1654
  undetectable = True
1645
1655
  if (
1646
1656
  browser_changes == 1
1647
- and browser_text not in ["chrome", "opera", "brave", "comet", "atlas"]
1657
+ and browser_text not in [
1658
+ "chrome", "opera", "brave", "comet", "atlas", "chromium"
1659
+ ]
1648
1660
  and undetectable
1649
1661
  ):
1650
1662
  message = (
@@ -1712,7 +1724,11 @@ def pytest_configure(config):
1712
1724
  if sb_config.headless2 and sb_config.browser == "firefox":
1713
1725
  sb_config.headless2 = False # Only for Chromium browsers
1714
1726
  sb_config.headless = True # Firefox has regular headless
1715
- elif sb_config.browser not in ["chrome", "edge"]:
1727
+ elif (
1728
+ sb_config.browser not in [
1729
+ "chrome", "edge", "opera", "brave", "comet", "atlas", "chromium"
1730
+ ]
1731
+ ):
1716
1732
  sb_config.headless2 = False # Only for Chromium browsers
1717
1733
  sb_config.headed = config.getoption("headed")
1718
1734
  sb_config.xvfb = config.getoption("xvfb")
@@ -1727,7 +1743,7 @@ def pytest_configure(config):
1727
1743
  sb_config.extension_dir = config.getoption("extension_dir")
1728
1744
  sb_config.disable_features = config.getoption("disable_features")
1729
1745
  sb_config.binary_location = config.getoption("binary_location")
1730
- if hasattr(sb_config, "_cdp_bin_loc") and sb_config._cdp_bin_loc:
1746
+ if getattr(sb_config, "_cdp_bin_loc", None):
1731
1747
  sb_config.binary_location = sb_config._cdp_bin_loc
1732
1748
  elif not sb_config.binary_location:
1733
1749
  if (
@@ -1758,7 +1774,9 @@ def pytest_configure(config):
1758
1774
  bin_loc = detect_b_ver.get_binary_location("atlas")
1759
1775
  if bin_loc and os.path.exists(bin_loc):
1760
1776
  sb_config.binary_location = bin_loc
1761
- if config.getoption("use_cft") and not sb_config.binary_location:
1777
+ if config.getoption("use_chromium") and not sb_config.binary_location:
1778
+ sb_config.binary_location = "_chromium_"
1779
+ elif config.getoption("use_cft") and not sb_config.binary_location:
1762
1780
  sb_config.binary_location = "cft"
1763
1781
  elif config.getoption("use_chs") and not sb_config.binary_location:
1764
1782
  sb_config.binary_location = "chs"
@@ -1918,26 +1936,16 @@ def pytest_configure(config):
1918
1936
  or " -n=" in arg_join
1919
1937
  or " -n" in arg_join
1920
1938
  or "-c" in sys_argv
1921
- or (
1922
- "addopts" in config.inicfg.keys()
1923
- and (
1924
- "-n=" in config.inicfg["addopts"]
1925
- or "-n " in config.inicfg["addopts"]
1926
- or "-n" in config.inicfg["addopts"]
1927
- )
1928
- )
1939
+ or "-n=" in config.getini("addopts")
1940
+ or "-n " in config.getini("addopts")
1941
+ or "-n" in config.getini("addopts")
1929
1942
  ):
1930
1943
  sb_config._multithreaded = True
1931
1944
  if (
1932
1945
  "--html" in sys_argv
1933
1946
  or " --html=" in arg_join
1934
- or (
1935
- "addopts" in config.inicfg.keys()
1936
- and (
1937
- "--html=" in config.inicfg["addopts"]
1938
- or "--html " in config.inicfg["addopts"]
1939
- )
1940
- )
1947
+ or "--html=" in config.getini("addopts")
1948
+ or "--html " in config.getini("addopts")
1941
1949
  ):
1942
1950
  sb_config._using_html_report = True
1943
1951
  sb_config._html_report_name = config.getoption("htmlpath")
@@ -2195,16 +2203,12 @@ def pytest_runtest_teardown(item):
2195
2203
  self = item._testcase
2196
2204
  with suppress(Exception):
2197
2205
  if (
2198
- hasattr(self, "driver")
2199
- and self.driver
2206
+ getattr(self, "driver", None)
2200
2207
  and "--pdb" not in sys_argv
2201
2208
  ):
2202
2209
  if not (is_windows or self.driver.service.process):
2203
2210
  self.driver.quit()
2204
- elif (
2205
- hasattr(sb_config, "_sb_pdb_driver")
2206
- and sb_config._sb_pdb_driver
2207
- ):
2211
+ elif getattr(sb_config, "_sb_pdb_driver", None):
2208
2212
  with suppress(Exception):
2209
2213
  if (
2210
2214
  not is_windows
@@ -2214,32 +2218,18 @@ def pytest_runtest_teardown(item):
2214
2218
  sb_config._sb_pdb_driver = None
2215
2219
  with suppress(Exception):
2216
2220
  if (
2217
- hasattr(self, "_xvfb_display")
2218
- and self._xvfb_display
2221
+ getattr(self, "_xvfb_display", None)
2219
2222
  and hasattr(self._xvfb_display, "stop")
2220
- and (
2221
- not hasattr(sb_config, "reuse_session")
2222
- or (
2223
- hasattr(sb_config, "reuse_session")
2224
- and not sb_config.reuse_session
2225
- )
2226
- )
2223
+ and not getattr(sb_config, "reuse_session", None)
2227
2224
  ):
2228
2225
  self.headless_active = False
2229
2226
  sb_config.headless_active = False
2230
2227
  self._xvfb_display.stop()
2231
2228
  self._xvfb_display = None
2232
2229
  if (
2233
- hasattr(sb_config, "_virtual_display")
2234
- and sb_config._virtual_display
2230
+ getattr(sb_config, "_virtual_display", None)
2235
2231
  and hasattr(sb_config._virtual_display, "stop")
2236
- and (
2237
- not hasattr(sb_config, "reuse_session")
2238
- or (
2239
- hasattr(sb_config, "reuse_session")
2240
- and not sb_config.reuse_session
2241
- )
2242
- )
2232
+ and not getattr(sb_config, "reuse_session", None)
2243
2233
  ):
2244
2234
  sb_config._virtual_display.stop()
2245
2235
  sb_config._virtual_display = None
@@ -2338,12 +2328,9 @@ def _perform_pytest_unconfigure_(config):
2338
2328
  else:
2339
2329
  start_time = reporter._session_start.time # (pytest >= 8.4.0)
2340
2330
  duration = time.time() - start_time
2341
- if (
2342
- (hasattr(sb_config, "multi_proxy") and not sb_config.multi_proxy)
2343
- or not hasattr(sb_config, "multi_proxy")
2344
- ):
2331
+ if not getattr(sb_config, "multi_proxy", None):
2345
2332
  proxy_helper.remove_proxy_zip_if_present()
2346
- if hasattr(sb_config, "reuse_session") and sb_config.reuse_session:
2333
+ if getattr(sb_config, "reuse_session", None):
2347
2334
  # Close the shared browser session
2348
2335
  if sb_config.shared_driver:
2349
2336
  try:
@@ -2360,14 +2347,13 @@ def _perform_pytest_unconfigure_(config):
2360
2347
  sb_config.shared_driver = None
2361
2348
  with suppress(Exception):
2362
2349
  if (
2363
- hasattr(sb_config, "_virtual_display")
2364
- and sb_config._virtual_display
2350
+ getattr(sb_config, "_virtual_display", None)
2365
2351
  and hasattr(sb_config._virtual_display, "stop")
2366
2352
  ):
2367
2353
  sb_config._virtual_display.stop()
2368
2354
  sb_config._virtual_display = None
2369
2355
  sb_config.headless_active = False
2370
- if hasattr(sb_config, "_vd_list") and sb_config._vd_list:
2356
+ if getattr(sb_config, "_vd_list", None):
2371
2357
  if isinstance(sb_config._vd_list, list):
2372
2358
  for display in sb_config._vd_list:
2373
2359
  if display:
@@ -2382,7 +2368,7 @@ def _perform_pytest_unconfigure_(config):
2382
2368
  shared_utils.make_dir_files_writable("./assets/")
2383
2369
  log_helper.clear_empty_logs()
2384
2370
  # Dashboard post-processing: Disable time-based refresh and stamp complete
2385
- if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
2371
+ if not getattr(sb_config, "dashboard", None):
2386
2372
  html_report_path = None
2387
2373
  the_html_r = None
2388
2374
  abs_path = os.path.abspath(".")
@@ -2687,11 +2673,11 @@ def pytest_unconfigure(config):
2687
2673
  )
2688
2674
  ):
2689
2675
  return
2690
- if hasattr(sb_config, "_multithreaded") and sb_config._multithreaded:
2676
+ if getattr(sb_config, "_multithreaded", None):
2691
2677
  import fasteners
2692
2678
 
2693
2679
  dash_lock = fasteners.InterProcessLock(constants.Dashboard.LOCKFILE)
2694
- if hasattr(sb_config, "dashboard") and sb_config.dashboard:
2680
+ if getattr(sb_config, "dashboard", None):
2695
2681
  # Multi-threaded tests with the Dashboard
2696
2682
  abs_path = os.path.abspath(".")
2697
2683
  dash_lock_file = constants.Dashboard.LOCKFILE
@@ -10,13 +10,13 @@ Example -->
10
10
  ```python
11
11
  from seleniumbase import SB
12
12
 
13
- with SB(uc=True) as sb: # Many args! Eg. SB(browser="edge")
14
- sb.open("https://google.com/ncr")
15
- sb.type('[name="q"]', "SeleniumBase on GitHub\n")
16
- sb.click('a[href*="github.com/seleniumbase"]')
17
- sb.highlight("div.Layout-main")
18
- sb.highlight("div.Layout-sidebar")
19
- sb.sleep(0.5)
13
+ with SB(uc=True, test=True) as sb:
14
+ url = "https://google.com/ncr"
15
+ sb.activate_cdp_mode(url)
16
+ sb.type('[title="Search"]', "SeleniumBase GitHub page")
17
+ sb.click("div:not([jsname]) > * > input")
18
+ sb.sleep(2)
19
+ print(sb.get_page_title())
20
20
  ```
21
21
 
22
22
  # (The browser exits automatically after the "with" block ends.)
@@ -123,6 +123,7 @@ def SB(
123
123
  wfa=None, # Shortcut / Duplicate of "wait_for_angularjs".
124
124
  cft=None, # Use "Chrome for Testing"
125
125
  chs=None, # Use "Chrome-Headless-Shell"
126
+ use_chromium=None, # Use base "Chromium"
126
127
  save_screenshot=None, # Save a screenshot at the end of each test.
127
128
  no_screenshot=None, # No screenshots saved unless tests directly ask it.
128
129
  page_load_strategy=None, # Set Chrome PLS to "normal", "eager", or "none".
@@ -144,14 +145,13 @@ def SB(
144
145
  .. code-block:: python
145
146
  from seleniumbase import SB
146
147
 
147
- with SB() as sb: # Many args! Eg. SB(browser="edge")
148
- sb.open("https://google.com/ncr")
149
- sb.type('[name="q"]', "SeleniumBase on GitHub")
150
- sb.submit('[name="q"]')
151
- sb.click('a[href*="github.com/seleniumbase"]')
152
- sb.highlight("div.Layout-main")
153
- sb.highlight("div.Layout-sidebar")
154
- sb.sleep(0.5)
148
+ with SB(uc=True, test=True) as sb:
149
+ url = "https://google.com/ncr"
150
+ sb.activate_cdp_mode(url)
151
+ sb.type('[title="Search"]', "SeleniumBase GitHub page")
152
+ sb.click("div:not([jsname]) > * > input")
153
+ sb.sleep(2)
154
+ print(sb.get_page_title())
155
155
 
156
156
  Optional Parameters:
157
157
  --------------------
@@ -700,11 +700,15 @@ def SB(
700
700
  if arg.startswith("--bl="):
701
701
  binary_location = arg.split("--bl=")[1]
702
702
  break
703
- if cft and not binary_location:
703
+ if use_chromium and not binary_location:
704
+ binary_location = "_chromium_"
705
+ elif cft and not binary_location:
704
706
  binary_location = "cft"
705
707
  elif chs and not binary_location:
706
708
  binary_location = "chs"
707
- if "--cft" in sys_argv and not binary_location:
709
+ if "--use-chromium" in sys_argv and not binary_location:
710
+ binary_location = "_chromium_"
711
+ elif "--cft" in sys_argv and not binary_location:
708
712
  binary_location = "cft"
709
713
  elif "--chs" in sys_argv and not binary_location:
710
714
  binary_location = "chs"
@@ -805,7 +809,7 @@ def SB(
805
809
  and browser not in ["chrome", "opera", "brave", "comet", "atlas"]
806
810
  ):
807
811
  message = (
808
- '\n Undetected-Chromedriver Mode ONLY supports Chrome!'
812
+ '\n Undetected-Chromedriver Mode ONLY supports Chromium browsers!'
809
813
  '\n ("uc=True" / "undetectable=True" / "--uc")'
810
814
  '\n (Your browser choice was: "%s".)'
811
815
  '\n (Will use "%s" without UC Mode.)\n' % (browser, browser)
@@ -831,7 +835,7 @@ def SB(
831
835
  if headless2 and browser == "firefox":
832
836
  headless2 = False # Only for Chromium browsers
833
837
  headless = True # Firefox has regular headless
834
- elif browser not in ["chrome", "edge"]:
838
+ elif browser not in ["chrome", "edge", "opera", "brave", "comet", "atlas"]:
835
839
  headless2 = False # Only for Chromium browsers
836
840
  if not headless and not headless2:
837
841
  headed = True