seleniumbase 4.33.4__py3-none-any.whl → 4.34.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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