seleniumbase 4.33.4__py3-none-any.whl → 4.34.2__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 (33) hide show
  1. seleniumbase/__version__.py +1 -1
  2. seleniumbase/behave/behave_sb.py +10 -2
  3. seleniumbase/console_scripts/run.py +6 -2
  4. seleniumbase/console_scripts/sb_commander.py +5 -5
  5. seleniumbase/console_scripts/sb_install.py +235 -6
  6. seleniumbase/console_scripts/sb_mkdir.py +1 -0
  7. seleniumbase/core/browser_launcher.py +358 -105
  8. seleniumbase/core/log_helper.py +33 -12
  9. seleniumbase/core/proxy_helper.py +35 -30
  10. seleniumbase/core/sb_cdp.py +277 -74
  11. seleniumbase/core/settings_parser.py +2 -0
  12. seleniumbase/core/style_sheet.py +10 -0
  13. seleniumbase/fixtures/base_case.py +216 -127
  14. seleniumbase/fixtures/constants.py +3 -0
  15. seleniumbase/fixtures/js_utils.py +2 -0
  16. seleniumbase/fixtures/page_actions.py +7 -2
  17. seleniumbase/fixtures/shared_utils.py +25 -0
  18. seleniumbase/plugins/driver_manager.py +28 -0
  19. seleniumbase/plugins/pytest_plugin.py +110 -0
  20. seleniumbase/plugins/sb_manager.py +41 -0
  21. seleniumbase/plugins/selenium_plugin.py +9 -0
  22. seleniumbase/undetected/cdp_driver/_contradict.py +3 -3
  23. seleniumbase/undetected/cdp_driver/browser.py +8 -6
  24. seleniumbase/undetected/cdp_driver/cdp_util.py +3 -0
  25. seleniumbase/undetected/cdp_driver/config.py +0 -1
  26. seleniumbase/undetected/cdp_driver/element.py +22 -20
  27. seleniumbase/undetected/patcher.py +20 -5
  28. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/LICENSE +1 -1
  29. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/METADATA +111 -86
  30. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/RECORD +33 -33
  31. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/WHEEL +1 -1
  32. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/entry_points.txt +0 -0
  33. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/top_level.txt +0 -0
@@ -400,6 +400,7 @@ class ValidBinaries:
400
400
  "google-chrome-beta",
401
401
  "google-chrome-dev",
402
402
  "google-chrome-unstable",
403
+ "chrome-headless-shell",
403
404
  "brave-browser",
404
405
  "brave-browser-stable",
405
406
  "brave",
@@ -418,6 +419,7 @@ class ValidBinaries:
418
419
  "Google Chrome",
419
420
  "Chromium",
420
421
  "Google Chrome for Testing",
422
+ "chrome-headless-shell",
421
423
  "Google Chrome Beta",
422
424
  "Google Chrome Dev",
423
425
  "Brave Browser",
@@ -429,6 +431,7 @@ class ValidBinaries:
429
431
  valid_chrome_binaries_on_windows = [
430
432
  "chrome.exe",
431
433
  "chromium.exe",
434
+ "chrome-headless-shell.exe",
432
435
  "brave.exe",
433
436
  "opera.exe",
434
437
  ]
@@ -1188,6 +1188,8 @@ def highlight_with_jquery_2(driver, message, selector, o_bs, msg_dur):
1188
1188
  def get_active_element_css(driver):
1189
1189
  from seleniumbase.js_code import active_css_js
1190
1190
 
1191
+ if shared_utils.is_cdp_swap_needed(driver):
1192
+ return driver.cdp.get_active_element_css()
1191
1193
  return execute_script(driver, active_css_js.get_active_element_css)
1192
1194
 
1193
1195
 
@@ -1342,7 +1342,8 @@ def save_page_source(driver, name, folder=None):
1342
1342
  """
1343
1343
  from seleniumbase.core import log_helper
1344
1344
 
1345
- _reconnect_if_disconnected(driver)
1345
+ if not __is_cdp_swap_needed(driver):
1346
+ _reconnect_if_disconnected(driver) # If disconnected without CDP
1346
1347
  if not name.endswith(".html"):
1347
1348
  name = name + ".html"
1348
1349
  if folder:
@@ -1353,7 +1354,11 @@ def save_page_source(driver, name, folder=None):
1353
1354
  html_file_path = os.path.join(file_path, name)
1354
1355
  else:
1355
1356
  html_file_path = name
1356
- page_source = driver.page_source
1357
+ page_source = None
1358
+ if __is_cdp_swap_needed(driver):
1359
+ page_source = driver.cdp.get_page_source()
1360
+ else:
1361
+ page_source = driver.page_source
1357
1362
  html_file = codecs.open(html_file_path, "w+", "utf-8")
1358
1363
  rendered_source = log_helper.get_html_source_with_base_href(
1359
1364
  driver, page_source
@@ -1,6 +1,7 @@
1
1
  """Shared utility methods"""
2
2
  import colorama
3
3
  import os
4
+ import pathlib
4
5
  import platform
5
6
  import sys
6
7
  import time
@@ -128,6 +129,30 @@ def is_chrome_130_or_newer(self, binary_location=None):
128
129
  return False
129
130
 
130
131
 
132
+ def make_dir_files_writable(dir_path):
133
+ # Make all files in the given directory writable.
134
+ for file_path in pathlib.Path(dir_path).glob("*"):
135
+ if file_path.is_file():
136
+ mode = os.stat(file_path).st_mode
137
+ mode |= (mode & 0o444) >> 1 # copy R bits to W
138
+ with suppress(Exception):
139
+ os.chmod(file_path, mode)
140
+
141
+
142
+ def make_writable(file_path):
143
+ # Set permissions to: "If you can read it, you can write it."
144
+ mode = os.stat(file_path).st_mode
145
+ mode |= (mode & 0o444) >> 1 # copy R bits to W
146
+ os.chmod(file_path, mode)
147
+
148
+
149
+ def make_executable(file_path):
150
+ # Set permissions to: "If you can read it, you can execute it."
151
+ mode = os.stat(file_path).st_mode
152
+ mode |= (mode & 0o444) >> 2 # copy R bits to X
153
+ os.chmod(file_path, mode)
154
+
155
+
131
156
  def format_exc(exception, message):
132
157
  """Formats an exception message to make the output cleaner."""
133
158
  from selenium.common.exceptions import ElementNotVisibleException
@@ -530,6 +530,34 @@ def Driver(
530
530
  break
531
531
  count += 1
532
532
  user_agent = agent
533
+ found_bl = None
534
+ if binary_location is None and "--binary-location" in arg_join:
535
+ count = 0
536
+ for arg in sys_argv:
537
+ if arg.startswith("--binary-location="):
538
+ found_bl = arg.split("--binary-location=")[1]
539
+ break
540
+ elif arg == "--binary-location" and len(sys_argv) > count + 1:
541
+ found_bl = sys_argv[count + 1]
542
+ if found_bl.startswith("-"):
543
+ found_bl = None
544
+ break
545
+ count += 1
546
+ if found_bl:
547
+ binary_location = found_bl
548
+ if binary_location is None and "--bl=" in arg_join:
549
+ for arg in sys_argv:
550
+ if arg.startswith("--bl="):
551
+ binary_location = arg.split("--bl=")[1]
552
+ break
553
+ if (
554
+ binary_location
555
+ and binary_location.lower() == "chs"
556
+ and browser == "chrome"
557
+ ):
558
+ headless = True
559
+ headless1 = False
560
+ headless2 = False
533
561
  recorder_mode = False
534
562
  if recorder_ext:
535
563
  recorder_mode = True
@@ -656,6 +656,7 @@ def pytest_addoption(parser):
656
656
  parser.addoption(
657
657
  "--binary_location",
658
658
  "--binary-location",
659
+ "--bl",
659
660
  action="store",
660
661
  dest="binary_location",
661
662
  default=None,
@@ -1371,6 +1372,7 @@ def pytest_addoption(parser):
1371
1372
 
1372
1373
  arg_join = " ".join(sys_argv)
1373
1374
  sb_config._browser_shortcut = None
1375
+ sb_config._vd_list = []
1374
1376
 
1375
1377
  # SeleniumBase does not support pytest-timeout due to hanging browsers.
1376
1378
  for arg in sys_argv:
@@ -1573,6 +1575,14 @@ def pytest_configure(config):
1573
1575
  sb_config.extension_dir = config.getoption("extension_dir")
1574
1576
  sb_config.disable_features = config.getoption("disable_features")
1575
1577
  sb_config.binary_location = config.getoption("binary_location")
1578
+ if (
1579
+ sb_config.binary_location
1580
+ and sb_config.binary_location.lower() == "chs"
1581
+ and sb_config.browser == "chrome"
1582
+ ):
1583
+ sb_config.headless = True
1584
+ sb_config.headless1 = False
1585
+ sb_config.headless2 = False
1576
1586
  sb_config.driver_version = config.getoption("driver_version")
1577
1587
  sb_config.page_load_strategy = config.getoption("page_load_strategy")
1578
1588
  sb_config.with_testing_base = config.getoption("with_testing_base")
@@ -1709,6 +1719,7 @@ def pytest_configure(config):
1709
1719
  sb_config._saved_dashboard_pie = None # Copy of pie chart for html report
1710
1720
  sb_config._dash_final_summary = None # Dash status to add to html report
1711
1721
  sb_config._html_report_name = None # The name of the pytest html report
1722
+ sb_config._html_report_copy = None # The copy of the pytest html report
1712
1723
 
1713
1724
  arg_join = " ".join(sys_argv)
1714
1725
  if (
@@ -1742,6 +1753,7 @@ def pytest_configure(config):
1742
1753
  if sb_config.dashboard:
1743
1754
  if sb_config._html_report_name == "dashboard.html":
1744
1755
  sb_config._dash_is_html_report = True
1756
+ sb_config._html_report_copy = "last_report.html"
1745
1757
 
1746
1758
  # Recorder Mode does not support multi-threaded / multi-process runs.
1747
1759
  if sb_config.recorder_mode and sb_config._multithreaded:
@@ -2015,6 +2027,13 @@ def pytest_runtest_teardown(item):
2015
2027
  hasattr(self, "_xvfb_display")
2016
2028
  and self._xvfb_display
2017
2029
  and hasattr(self._xvfb_display, "stop")
2030
+ and (
2031
+ not hasattr(sb_config, "reuse_session")
2032
+ or (
2033
+ hasattr(sb_config, "reuse_session")
2034
+ and not sb_config.reuse_session
2035
+ )
2036
+ )
2018
2037
  ):
2019
2038
  self.headless_active = False
2020
2039
  sb_config.headless_active = False
@@ -2024,6 +2043,13 @@ def pytest_runtest_teardown(item):
2024
2043
  hasattr(sb_config, "_virtual_display")
2025
2044
  and sb_config._virtual_display
2026
2045
  and hasattr(sb_config._virtual_display, "stop")
2046
+ and (
2047
+ not hasattr(sb_config, "reuse_session")
2048
+ or (
2049
+ hasattr(sb_config, "reuse_session")
2050
+ and not sb_config.reuse_session
2051
+ )
2052
+ )
2027
2053
  ):
2028
2054
  sb_config._virtual_display.stop()
2029
2055
  sb_config._virtual_display = None
@@ -2137,10 +2163,28 @@ def _perform_pytest_unconfigure_(config):
2137
2163
  except Exception:
2138
2164
  pass
2139
2165
  sb_config.shared_driver = None
2166
+ with suppress(Exception):
2167
+ if (
2168
+ hasattr(sb_config, "_virtual_display")
2169
+ and sb_config._virtual_display
2170
+ and hasattr(sb_config._virtual_display, "stop")
2171
+ ):
2172
+ sb_config._virtual_display.stop()
2173
+ sb_config._virtual_display = None
2174
+ sb_config.headless_active = False
2175
+ if hasattr(sb_config, "_vd_list") and sb_config._vd_list:
2176
+ if isinstance(sb_config._vd_list, list):
2177
+ for display in sb_config._vd_list:
2178
+ if display:
2179
+ with suppress(Exception):
2180
+ display.stop()
2140
2181
  if hasattr(sb_config, "log_path") and sb_config.item_count > 0:
2141
2182
  log_helper.archive_logs_if_set(
2142
2183
  constants.Logs.LATEST + "/", sb_config.archive_logs
2143
2184
  )
2185
+ if os.path.exists("./assets/"): # Used by pytest-html reports
2186
+ with suppress(Exception):
2187
+ shared_utils.make_dir_files_writable("./assets/")
2144
2188
  log_helper.clear_empty_logs()
2145
2189
  # Dashboard post-processing: Disable time-based refresh and stamp complete
2146
2190
  if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
@@ -2151,6 +2195,10 @@ def _perform_pytest_unconfigure_(config):
2151
2195
  html_report_path = os.path.join(
2152
2196
  abs_path, sb_config._html_report_name
2153
2197
  )
2198
+ if sb_config._html_report_copy:
2199
+ html_report_path_copy = os.path.join(
2200
+ abs_path, sb_config._html_report_copy
2201
+ )
2154
2202
  if (
2155
2203
  sb_config._using_html_report
2156
2204
  and html_report_path
@@ -2184,6 +2232,9 @@ def _perform_pytest_unconfigure_(config):
2184
2232
  the_html_r = the_html_r.replace(
2185
2233
  ph_link, "%s and %s" % (sb_link, ph_link)
2186
2234
  )
2235
+ the_html_r = the_html_r.replace(
2236
+ "findAll('.collapsible", "//findAll('.collapsible"
2237
+ )
2187
2238
  the_html_r = the_html_r.replace(
2188
2239
  "mediaName.innerText", "//mediaName.innerText"
2189
2240
  )
@@ -2201,6 +2252,31 @@ def _perform_pytest_unconfigure_(config):
2201
2252
  )
2202
2253
  with open(html_report_path, "w", encoding="utf-8") as f:
2203
2254
  f.write(the_html_r) # Finalize the HTML report
2255
+ with suppress(Exception):
2256
+ shared_utils.make_writable(html_report_path)
2257
+ with open(html_report_path_copy, "w", encoding="utf-8") as f:
2258
+ f.write(the_html_r) # Finalize the HTML report copy
2259
+ with suppress(Exception):
2260
+ shared_utils.make_writable(html_report_path_copy)
2261
+ assets_style = "./assets/style.css"
2262
+ if os.path.exists(assets_style):
2263
+ html_style = None
2264
+ with open(assets_style, "r", encoding="utf-8") as f:
2265
+ html_style = f.read()
2266
+ if html_style:
2267
+ html_style = html_style.replace("top: -50px;", "top: 2px;")
2268
+ html_style = html_style.replace("+ 50px)", "+ 40px)")
2269
+ html_style = html_style.replace("ht: 240px;", "ht: 228px;")
2270
+ html_style = html_style.replace(
2271
+ "- 80px);", "- 80px);\n margin-bottom: -42px;"
2272
+ )
2273
+ html_style = html_style.replace(".collapsible", ".oldc")
2274
+ html_style = html_style.replace(" (hide details)", "")
2275
+ html_style = html_style.replace(" (show details)", "")
2276
+ with open(assets_style, "w", encoding="utf-8") as f:
2277
+ f.write(html_style)
2278
+ with suppress(Exception):
2279
+ shared_utils.make_writable(assets_style)
2204
2280
  # Done with "pytest_unconfigure" unless using the Dashboard
2205
2281
  return
2206
2282
  stamp = ""
@@ -2282,12 +2358,37 @@ def _perform_pytest_unconfigure_(config):
2282
2358
  )
2283
2359
  with open(dashboard_path, "w", encoding="utf-8") as f:
2284
2360
  f.write(the_html_d) # Finalize the dashboard
2361
+ with suppress(Exception):
2362
+ shared_utils.make_writable(dashboard_path)
2363
+ assets_style = "./assets/style.css"
2364
+ if os.path.exists(assets_style):
2365
+ html_style = None
2366
+ with open(assets_style, "r", encoding="utf-8") as f:
2367
+ html_style = f.read()
2368
+ if html_style:
2369
+ html_style = html_style.replace("top: -50px;", "top: 2px;")
2370
+ html_style = html_style.replace("+ 50px)", "+ 40px)")
2371
+ html_style = html_style.replace("ht: 240px;", "ht: 228px;")
2372
+ html_style = html_style.replace(
2373
+ "- 80px);", "- 80px);\n margin-bottom: -42px;"
2374
+ )
2375
+ html_style = html_style.replace(".collapsible", ".oldc")
2376
+ html_style = html_style.replace(" (hide details)", "")
2377
+ html_style = html_style.replace(" (show details)", "")
2378
+ with open(assets_style, "w", encoding="utf-8") as f:
2379
+ f.write(html_style)
2380
+ with suppress(Exception):
2381
+ shared_utils.make_writable(assets_style)
2285
2382
  # Part 2: Appending a pytest html report with dashboard data
2286
2383
  html_report_path = None
2287
2384
  if sb_config._html_report_name:
2288
2385
  html_report_path = os.path.join(
2289
2386
  abs_path, sb_config._html_report_name
2290
2387
  )
2388
+ if sb_config._html_report_copy:
2389
+ html_report_path_copy = os.path.join(
2390
+ abs_path, sb_config._html_report_copy
2391
+ )
2291
2392
  if (
2292
2393
  sb_config._using_html_report
2293
2394
  and html_report_path
@@ -2341,6 +2442,9 @@ def _perform_pytest_unconfigure_(config):
2341
2442
  the_html_r = the_html_r.replace(
2342
2443
  ph_link, "%s and %s" % (sb_link, ph_link)
2343
2444
  )
2445
+ the_html_r = the_html_r.replace(
2446
+ "findAll('.collapsible", "//findAll('.collapsible"
2447
+ )
2344
2448
  the_html_r = the_html_r.replace(
2345
2449
  "mediaName.innerText", "//mediaName.innerText"
2346
2450
  )
@@ -2358,6 +2462,12 @@ def _perform_pytest_unconfigure_(config):
2358
2462
  )
2359
2463
  with open(html_report_path, "w", encoding="utf-8") as f:
2360
2464
  f.write(the_html_r) # Finalize the HTML report
2465
+ with suppress(Exception):
2466
+ shared_utils.make_writable(html_report_path)
2467
+ with open(html_report_path_copy, "w", encoding="utf-8") as f:
2468
+ f.write(the_html_r) # Finalize the HTML report copy
2469
+ with suppress(Exception):
2470
+ shared_utils.make_writable(html_report_path_copy)
2361
2471
  except KeyboardInterrupt:
2362
2472
  pass
2363
2473
  except Exception:
@@ -568,6 +568,34 @@ def SB(
568
568
  break
569
569
  count += 1
570
570
  user_agent = agent
571
+ found_bl = None
572
+ if binary_location is None and "--binary-location" in arg_join:
573
+ count = 0
574
+ for arg in sys_argv:
575
+ if arg.startswith("--binary-location="):
576
+ found_bl = arg.split("--binary-location=")[1]
577
+ break
578
+ elif arg == "--binary-location" and len(sys_argv) > count + 1:
579
+ found_bl = sys_argv[count + 1]
580
+ if found_bl.startswith("-"):
581
+ found_bl = None
582
+ break
583
+ count += 1
584
+ if found_bl:
585
+ binary_location = found_bl
586
+ if binary_location is None and "--bl=" in arg_join:
587
+ for arg in sys_argv:
588
+ if arg.startswith("--bl="):
589
+ binary_location = arg.split("--bl=")[1]
590
+ break
591
+ if (
592
+ binary_location
593
+ and binary_location.lower() == "chs"
594
+ and browser == "chrome"
595
+ ):
596
+ headless = True
597
+ headless1 = False
598
+ headless2 = False
571
599
  recorder_mode = False
572
600
  if recorder_ext:
573
601
  recorder_mode = True
@@ -1256,6 +1284,19 @@ def SB(
1256
1284
  print(traceback.format_exc().strip())
1257
1285
  if test and not test_passed:
1258
1286
  print("********** ERROR: The test AND the tearDown() FAILED!")
1287
+ if (
1288
+ hasattr(sb_config, "_virtual_display")
1289
+ and sb_config._virtual_display
1290
+ and hasattr(sb_config._virtual_display, "stop")
1291
+ ):
1292
+ try:
1293
+ sb_config._virtual_display.stop()
1294
+ sb_config._virtual_display = None
1295
+ sb_config.headless_active = False
1296
+ except AttributeError:
1297
+ pass
1298
+ except Exception:
1299
+ pass
1259
1300
  end_time = time.time()
1260
1301
  run_time = end_time - start_time
1261
1302
  sb_config = sb_config_backup
@@ -397,6 +397,7 @@ class SeleniumBrowser(Plugin):
397
397
  parser.addoption(
398
398
  "--binary_location",
399
399
  "--binary-location",
400
+ "--bl",
400
401
  action="store",
401
402
  dest="binary_location",
402
403
  default=None,
@@ -1202,6 +1203,14 @@ class SeleniumBrowser(Plugin):
1202
1203
  test.test.extension_dir = self.options.extension_dir
1203
1204
  test.test.disable_features = self.options.disable_features
1204
1205
  test.test.binary_location = self.options.binary_location
1206
+ if (
1207
+ test.test.binary_location
1208
+ and test.test.binary_location.lower() == "chs"
1209
+ and test.test.browser == "chrome"
1210
+ ):
1211
+ test.test.headless = True
1212
+ test.test.headless1 = False
1213
+ test.test.headless2 = False
1205
1214
  test.test.driver_version = self.options.driver_version
1206
1215
  test.test.page_load_strategy = self.options.page_load_strategy
1207
1216
  test.test.chromium_arg = self.options.chromium_arg
@@ -31,12 +31,12 @@ class ContraDict(dict):
31
31
 
32
32
  def __init__(self, *args, **kwargs):
33
33
  super().__init__()
34
- silent = kwargs.pop("silent", False)
34
+ # silent = kwargs.pop("silent", False)
35
35
  _ = dict(*args, **kwargs)
36
36
 
37
37
  super().__setattr__("__dict__", self)
38
38
  for k, v in _.items():
39
- _check_key(k, self, False, silent)
39
+ _check_key(k, self, False, True)
40
40
  super().__setitem__(k, _wrap(self.__class__, v))
41
41
 
42
42
  def __setitem__(self, key, value):
@@ -90,7 +90,7 @@ _warning_names_message = """\n\
90
90
 
91
91
 
92
92
  def _check_key(
93
- key: str, mapping: _Mapping, boolean: bool = False, silent=False
93
+ key: str, mapping: _Mapping, boolean: bool = False, silent=True
94
94
  ):
95
95
  """Checks `key` and warns if needed.
96
96
  :param key:
@@ -10,6 +10,7 @@ import pathlib
10
10
  import pickle
11
11
  import re
12
12
  import shutil
13
+ import time
13
14
  import urllib.parse
14
15
  import urllib.request
15
16
  import warnings
@@ -30,8 +31,6 @@ def get_registered_instances():
30
31
 
31
32
 
32
33
  def deconstruct_browser():
33
- import time
34
-
35
34
  for _ in __registered__instances__:
36
35
  if not _.stopped:
37
36
  _.stop()
@@ -117,8 +116,13 @@ class Browser:
117
116
  port=port,
118
117
  **kwargs,
119
118
  )
120
- instance = cls(config)
121
- await instance.start()
119
+ try:
120
+ instance = cls(config)
121
+ await instance.start()
122
+ except Exception:
123
+ time.sleep(0.15)
124
+ instance = cls(config)
125
+ await instance.start()
122
126
  return instance
123
127
 
124
128
  def __init__(self, config: Config, **kwargs):
@@ -379,8 +383,6 @@ class Browser:
379
383
  --------------------------------
380
384
  Failed to connect to the browser
381
385
  --------------------------------
382
- Possibly because you are running as "root".
383
- If so, you may need to use no_sandbox=True.
384
386
  """
385
387
  )
386
388
  )
@@ -84,6 +84,9 @@ def __activate_virtual_display_as_needed(
84
84
  "\nX11 display failed! Will use regular xvfb!"
85
85
  )
86
86
  __activate_standard_virtual_display()
87
+ else:
88
+ sb_config._virtual_display = _xvfb_display
89
+ sb_config.headless_active = True
87
90
  except Exception as e:
88
91
  if hasattr(e, "msg"):
89
92
  print("\n" + str(e.msg))
@@ -125,7 +125,6 @@ class Config:
125
125
  "--deny-permission-prompts",
126
126
  "--disable-infobars",
127
127
  "--disable-breakpad",
128
- "--disable-component-update",
129
128
  "--disable-prompt-on-repost",
130
129
  "--disable-password-generation",
131
130
  "--disable-ipc-flooding-protection",
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
  import asyncio
3
- import json
4
3
  import logging
5
4
  import pathlib
6
5
  import secrets
@@ -387,18 +386,16 @@ class Element:
387
386
 
388
387
  async def get_js_attributes_async(self):
389
388
  return ContraDict(
390
- json.loads(
391
- await self.apply(
392
- """
393
- function (e) {
394
- let o = {}
395
- for(let k in e){
396
- o[k] = e[k]
389
+ await self.apply(
390
+ """
391
+ function (e) {
392
+ let o = {}
393
+ for(let k in e){
394
+ o[k] = e[k]
395
+ }
396
+ return o
397
397
  }
398
- return JSON.stringify(o)
399
- }
400
- """
401
- )
398
+ """
402
399
  )
403
400
  )
404
401
 
@@ -441,12 +438,15 @@ class Element:
441
438
  )
442
439
  )
443
440
  )
444
- if result and result[0]:
445
- if return_by_value:
446
- return result[0].value
447
- return result[0]
448
- elif result[1]:
449
- return result[1]
441
+ try:
442
+ if result and result[0]:
443
+ if return_by_value:
444
+ return result[0].value
445
+ return result[0]
446
+ elif result[1]:
447
+ return result[1]
448
+ except Exception:
449
+ return self
450
450
 
451
451
  async def get_position_async(self, abs=False) -> Position:
452
452
  if not self.parent or not self.object_id:
@@ -883,6 +883,8 @@ class Element:
883
883
  self,
884
884
  duration: typing.Union[float, int] = 0.5,
885
885
  color: typing.Optional[str] = "EE4488",
886
+ x_offset: typing.Union[float, int] = 0,
887
+ y_offset: typing.Union[float, int] = 0,
886
888
  ):
887
889
  """
888
890
  Displays for a short time a red dot on the element.
@@ -910,8 +912,8 @@ class Element:
910
912
  "width:8px;height:8px;border-radius:50%;background:#{};"
911
913
  "animation:show-pointer-ani {:.2f}s ease 1;"
912
914
  ).format(
913
- pos.center[0] - 4, # -4 to account for drawn circle itself (w,h)
914
- pos.center[1] - 4,
915
+ pos.center[0] + x_offset - 4, # -4 to account for the circle
916
+ pos.center[1] + y_offset - 4, # -4 to account for the circle
915
917
  color,
916
918
  duration,
917
919
  )
@@ -8,6 +8,8 @@ import sys
8
8
  import time
9
9
  import zipfile
10
10
  from contextlib import suppress
11
+ from seleniumbase.console_scripts import sb_install
12
+ from seleniumbase.fixtures import shared_utils
11
13
 
12
14
  logger = logging.getLogger(__name__)
13
15
  IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux"))
@@ -106,7 +108,14 @@ class Patcher(object):
106
108
  release = self.fetch_release_number()
107
109
  self.version_main = release.split(".")[0]
108
110
  self.version_full = release
109
- self.unzip_package(self.fetch_package())
111
+ if int(self.version_main) < 115:
112
+ self.unzip_package(self.fetch_package())
113
+ else:
114
+ sb_install.main(
115
+ override="chromedriver %s" % self.version_main,
116
+ intel_for_uc=shared_utils.is_arm_mac(),
117
+ force_uc=True,
118
+ )
110
119
  return self.patch()
111
120
 
112
121
  def patch(self):
@@ -121,6 +130,12 @@ class Patcher(object):
121
130
  path += "_%s" % self.version_main
122
131
  path = path.upper()
123
132
  logger.debug("Getting release number from %s" % path)
133
+ if self.version_main and int(self.version_main) > 114:
134
+ return (
135
+ sb_install.get_cft_latest_version_from_milestone(
136
+ str(self.version_main)
137
+ )
138
+ )
124
139
  return urlopen(self.url_repo + path).read().decode()
125
140
 
126
141
  def fetch_package(self):
@@ -187,7 +202,7 @@ class Patcher(object):
187
202
  with io.open(executable_path, "rb") as fh:
188
203
  if re.search(
189
204
  b"window.cdc_adoQpoasnfa76pfcZLmcfl_"
190
- b"(Array|Promise|Symbol|Object|Proxy|JSON)",
205
+ b"(Array|Promise|Symbol|Object|Proxy|JSON|Window)",
191
206
  fh.read()
192
207
  ):
193
208
  return False
@@ -210,14 +225,14 @@ class Patcher(object):
210
225
  file_bin = fh.read()
211
226
  file_bin = re.sub(
212
227
  b"window\\.cdc_[a-zA-Z0-9]{22}_"
213
- b"(Array|Promise|Symbol|Object|Proxy|JSON)"
214
- b" = window\\.(Array|Promise|Symbol|Object|Proxy|JSON);",
228
+ b"(Array|Promise|Symbol|Object|Proxy|JSON|Window) "
229
+ b"= window\\.(Array|Promise|Symbol|Object|Proxy|JSON|Window);",
215
230
  gen_js_whitespaces,
216
231
  file_bin,
217
232
  )
218
233
  file_bin = re.sub(
219
234
  b"window\\.cdc_[a-zA-Z0-9]{22}_"
220
- b"(Array|Promise|Symbol|Object|Proxy|JSON) \\|\\|",
235
+ b"(Array|Promise|Symbol|Object|Proxy|JSON|Window) \\|\\|",
221
236
  gen_js_whitespaces,
222
237
  file_bin,
223
238
  )
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2024 Michael Mintz
3
+ Copyright (c) 2014-2025 Michael Mintz
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal