seleniumbase 4.32.10a3__py3-none-any.whl → 4.32.12__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
sbase/steps.py CHANGED
@@ -1189,3 +1189,10 @@ def set_attributes(context, selector, attribute, value):
1189
1189
  if attribute.endswith("'") or attribute.endswith('"'):
1190
1190
  attribute = attribute[:-1]
1191
1191
  sb.set_attributes(selector, attribute, value)
1192
+
1193
+
1194
+ @step("Activate CDP Mode")
1195
+ @step("User activates CDP Mode")
1196
+ def activate_cdp_mode(context):
1197
+ sb = context.sb
1198
+ sb.activate_cdp_mode()
@@ -1,2 +1,2 @@
1
1
  # seleniumbase package
2
- __version__ = "4.32.10a3"
2
+ __version__ = "4.32.12"
@@ -1,11 +1,18 @@
1
+ import colorama
1
2
  import logging
2
3
  import math
4
+ import sys
3
5
  import time
4
6
  import warnings
5
7
  from contextlib import contextmanager
6
8
  from functools import wraps
7
9
  from seleniumbase.common.exceptions import TimeoutException
8
10
 
11
+ c1 = colorama.Fore.BLUE + colorama.Back.LIGHTYELLOW_EX
12
+ cr = colorama.Style.RESET_ALL
13
+ if "linux" in sys.platform:
14
+ c1 = cr = ""
15
+
9
16
 
10
17
  @contextmanager
11
18
  def print_runtime(description=None, limit=None):
@@ -44,19 +51,20 @@ def print_runtime(description=None, limit=None):
44
51
  end_time = time.time()
45
52
  run_time = end_time - start_time
46
53
  name = description
54
+ info = c1 + "<info>" + cr
47
55
  # Print times with a statistically significant number of decimal places
48
56
  if run_time < 0.0001:
49
- print("<info> - {%s} ran for %.7f seconds." % (name, run_time))
57
+ print("%s - {%s} ran for %.7f seconds." % (info, name, run_time))
50
58
  elif run_time < 0.001:
51
- print("<info> - {%s} ran for %.6f seconds." % (name, run_time))
59
+ print("%s - {%s} ran for %.6f seconds." % (info, name, run_time))
52
60
  elif run_time < 0.01:
53
- print("<info> - {%s} ran for %.5f seconds." % (name, run_time))
61
+ print("%s - {%s} ran for %.5f seconds." % (info, name, run_time))
54
62
  elif run_time < 0.1:
55
- print("<info> - {%s} ran for %.4f seconds." % (name, run_time))
63
+ print("%s - {%s} ran for %.4f seconds." % (info, name, run_time))
56
64
  elif run_time < 1:
57
- print("<info> - {%s} ran for %.3f seconds." % (name, run_time))
65
+ print("%s - {%s} ran for %.3f seconds." % (info, name, run_time))
58
66
  else:
59
- print("<info> - {%s} ran for %.2f seconds." % (name, run_time))
67
+ print("%s - {%s} ran for %.2f seconds." % (info, name, run_time))
60
68
  if limit and limit > 0 and run_time > limit:
61
69
  message = (
62
70
  "\n {%s} duration of %.2fs exceeded the time limit of %.2fs!"
@@ -144,7 +144,9 @@ def main():
144
144
  elif option.lower() in ("--gui", "--headed"):
145
145
  if "linux" in sys.platform:
146
146
  force_gui = True
147
- elif option.lower() in ("--uc", "--undetected", "--undetectable"):
147
+ elif option.lower() in (
148
+ "--uc", "--cdp", "--undetected", "--undetectable"
149
+ ):
148
150
  use_uc = True
149
151
  elif option.lower() in ("--rec-behave", "--behave", "--gherkin"):
150
152
  rec_behave = True
@@ -9,6 +9,7 @@ Usage:
9
9
 
10
10
  Options:
11
11
  --uc / --undetected (Use undetectable mode.)
12
+ --cdp (Same as "--uc" and "--undetectable".)
12
13
  --behave (Also output Behave/Gherkin files.)
13
14
 
14
15
  Output:
@@ -151,6 +152,7 @@ def do_recording(file_name, url, overwrite_enabled, use_chrome, window):
151
152
  command += " --edge"
152
153
  if (
153
154
  "--uc" in command_args
155
+ or "--cdp" in command_args
154
156
  or "--undetected" in command_args
155
157
  or "--undetectable" in command_args
156
158
  ):
@@ -193,6 +195,7 @@ def do_playback(file_name, use_chrome, window, demo_mode=False):
193
195
  command_args = sys.argv[2:]
194
196
  if (
195
197
  "--uc" in command_args
198
+ or "--cdp" in command_args
196
199
  or "--undetected" in command_args
197
200
  or "--undetectable" in command_args
198
201
  ):
@@ -544,6 +544,8 @@ def uc_open_with_cdp_mode(driver, url=None):
544
544
  )
545
545
  loop.run_until_complete(driver.cdp_base.wait(0))
546
546
 
547
+ gui_lock = fasteners.InterProcessLock(constants.MultiBrowser.PYAUTOGUILOCK)
548
+
547
549
  if (
548
550
  "chrome-extension://" in str(driver.cdp_base.main_tab)
549
551
  and len(driver.cdp_base.tabs) >= 2
@@ -553,7 +555,8 @@ def uc_open_with_cdp_mode(driver, url=None):
553
555
 
554
556
  for tab in driver.cdp_base.tabs[-1::-1]:
555
557
  if "chrome-extension://" not in str(tab):
556
- loop.run_until_complete(tab.activate())
558
+ with gui_lock:
559
+ loop.run_until_complete(tab.activate())
557
560
  break
558
561
 
559
562
  page_tab = None
@@ -566,14 +569,20 @@ def uc_open_with_cdp_mode(driver, url=None):
566
569
  break
567
570
  if page_tab:
568
571
  loop.run_until_complete(page_tab.aopen())
569
- loop.run_until_complete(page_tab.activate())
572
+ with gui_lock:
573
+ loop.run_until_complete(page_tab.activate())
570
574
 
571
575
  loop.run_until_complete(driver.cdp_base.update_targets())
572
576
  page = loop.run_until_complete(driver.cdp_base.get(url))
573
- loop.run_until_complete(page.activate())
577
+ with gui_lock:
578
+ loop.run_until_complete(page.activate())
574
579
  loop.run_until_complete(page.wait())
575
580
  if not safe_url:
576
581
  time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)
582
+ if IS_WINDOWS:
583
+ time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)
584
+ else:
585
+ time.sleep(0.012)
577
586
  cdp = types.SimpleNamespace()
578
587
  CDPM = sb_cdp.CDPMethods(loop, page, driver)
579
588
  cdp.get = CDPM.get
@@ -590,6 +599,9 @@ def uc_open_with_cdp_mode(driver, url=None):
590
599
  cdp.select = CDPM.select
591
600
  cdp.select_all = CDPM.select_all
592
601
  cdp.find_elements = CDPM.find_elements
602
+ cdp.find_visible_elements = CDPM.find_visible_elements
603
+ cdp.click_nth_element = CDPM.click_nth_element
604
+ cdp.click_nth_visible_element = CDPM.click_nth_visible_element
593
605
  cdp.click_link = CDPM.click_link
594
606
  cdp.tile_windows = CDPM.tile_windows
595
607
  cdp.get_all_cookies = CDPM.get_all_cookies
@@ -609,7 +621,6 @@ def uc_open_with_cdp_mode(driver, url=None):
609
621
  cdp.remove_element = CDPM.remove_element
610
622
  cdp.remove_from_dom = CDPM.remove_from_dom
611
623
  cdp.remove_elements = CDPM.remove_elements
612
- cdp.scroll_into_view = CDPM.scroll_into_view
613
624
  cdp.send_keys = CDPM.send_keys
614
625
  cdp.press_keys = CDPM.press_keys
615
626
  cdp.type = CDPM.type
@@ -672,16 +683,25 @@ def uc_open_with_cdp_mode(driver, url=None):
672
683
  cdp.is_checked = CDPM.is_checked
673
684
  cdp.is_element_present = CDPM.is_element_present
674
685
  cdp.is_element_visible = CDPM.is_element_visible
686
+ cdp.wait_for_element_visible = CDPM.wait_for_element_visible
687
+ cdp.assert_element = CDPM.assert_element
688
+ cdp.assert_element_visible = CDPM.assert_element_visible
675
689
  cdp.assert_element_present = CDPM.assert_element_present
676
690
  cdp.assert_element_absent = CDPM.assert_element_absent
677
- cdp.assert_element = CDPM.assert_element
678
- cdp.assert_element_visible = CDPM.assert_element
679
691
  cdp.assert_element_not_visible = CDPM.assert_element_not_visible
692
+ cdp.assert_element_attribute = CDPM.assert_element_attribute
680
693
  cdp.assert_title = CDPM.assert_title
694
+ cdp.assert_title_contains = CDPM.assert_title_contains
695
+ cdp.assert_url = CDPM.assert_url
696
+ cdp.assert_url_contains = CDPM.assert_url_contains
681
697
  cdp.assert_text = CDPM.assert_text
682
698
  cdp.assert_exact_text = CDPM.assert_exact_text
683
- cdp.scroll_down = CDPM.scroll_down
699
+ cdp.scroll_into_view = CDPM.scroll_into_view
700
+ cdp.scroll_to_y = CDPM.scroll_to_y
701
+ cdp.scroll_to_top = CDPM.scroll_to_top
702
+ cdp.scroll_to_bottom = CDPM.scroll_to_bottom
684
703
  cdp.scroll_up = CDPM.scroll_up
704
+ cdp.scroll_down = CDPM.scroll_down
685
705
  cdp.save_screenshot = CDPM.save_screenshot
686
706
  cdp.page = page # async world
687
707
  cdp.driver = driver.cdp_base # async world
@@ -1255,6 +1275,7 @@ def _uc_gui_click_captcha(
1255
1275
  with suppress(Exception):
1256
1276
  _uc_gui_click_x_y(driver, x, y, timeframe=0.32)
1257
1277
  if __is_cdp_swap_needed(driver):
1278
+ time.sleep(float(constants.UC.RECONNECT_TIME) / 2.0)
1258
1279
  return
1259
1280
  reconnect_time = (float(constants.UC.RECONNECT_TIME) / 2.0) + 0.6
1260
1281
  if IS_LINUX:
@@ -3,6 +3,7 @@ import os
3
3
  import shutil
4
4
  import sys
5
5
  import time
6
+ from contextlib import suppress
6
7
  from seleniumbase import config as sb_config
7
8
  from seleniumbase.config import settings
8
9
  from seleniumbase.fixtures import constants
@@ -281,14 +282,13 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None):
281
282
  sb_config._report_time = the_time
282
283
  sb_config._report_traceback = traceback_message
283
284
  sb_config._report_exception = exc_message
284
- try:
285
+ with suppress(Exception):
285
286
  if not os.path.exists(test_logpath):
286
287
  os.makedirs(test_logpath)
287
- except Exception:
288
- pass
289
- log_file = codecs.open(basic_file_path, "w+", "utf-8")
290
- log_file.writelines("\r\n".join(data_to_save))
291
- log_file.close()
288
+ with suppress(Exception):
289
+ log_file = codecs.open(basic_file_path, "w+", encoding="utf-8")
290
+ log_file.writelines("\r\n".join(data_to_save))
291
+ log_file.close()
292
292
 
293
293
 
294
294
  def log_skipped_test_data(test, test_logpath, driver, browser, reason):
@@ -297,16 +297,12 @@ def log_skipped_test_data(test, test_logpath, driver, browser, reason):
297
297
  browser_version = None
298
298
  driver_version = None
299
299
  driver_name = None
300
- try:
300
+ with suppress(Exception):
301
301
  browser_version = get_browser_version(driver)
302
- except Exception:
303
- pass
304
- try:
302
+ with suppress(Exception):
305
303
  driver_name, driver_version = get_driver_name_and_version(
306
304
  driver, browser
307
305
  )
308
- except Exception:
309
- pass
310
306
  if browser_version:
311
307
  headless = ""
312
308
  if test.headless and browser in ["chrome", "edge", "firefox"]:
@@ -368,13 +364,11 @@ def log_page_source(test_logpath, driver, source=None):
368
364
  "unresponsive, or closed prematurely!</h4>"
369
365
  )
370
366
  )
371
- try:
367
+ with suppress(Exception):
372
368
  if not os.path.exists(test_logpath):
373
369
  os.makedirs(test_logpath)
374
- except Exception:
375
- pass
376
370
  html_file_path = os.path.join(test_logpath, html_file_name)
377
- html_file = codecs.open(html_file_path, "w+", "utf-8")
371
+ html_file = codecs.open(html_file_path, "w+", encoding="utf-8")
378
372
  html_file.write(page_source)
379
373
  html_file.close()
380
374
 
@@ -543,7 +537,7 @@ def log_folder_setup(log_path, archive_logs=False):
543
537
  try:
544
538
  os.makedirs(log_path)
545
539
  except Exception:
546
- pass # Should only be reachable during multi-threaded runs
540
+ pass # Only reachable during multi-threaded runs
547
541
  else:
548
542
  saved_folder = "%s/../%s/" % (log_path, constants.Logs.SAVED)
549
543
  archived_folder = os.path.realpath(saved_folder) + "/"
@@ -551,7 +545,7 @@ def log_folder_setup(log_path, archive_logs=False):
551
545
  try:
552
546
  os.makedirs(archived_folder)
553
547
  except Exception:
554
- pass # Should only be reachable during multi-threaded runs
548
+ pass # Only reachable during multi-threaded runs
555
549
  archived_logs = "%slogs_%s" % (archived_folder, int(time.time()))
556
550
  if len(os.listdir(log_path)) > 0:
557
551
  try:
@@ -20,8 +20,16 @@ class CDPMethods():
20
20
  self.driver = driver
21
21
 
22
22
  def __slow_mode_pause_if_set(self):
23
- if hasattr(sb_config, "slow_mode") and sb_config.slow_mode:
24
- time.sleep(0.16)
23
+ if (
24
+ (hasattr(sb_config, "demo_mode") and sb_config.demo_mode)
25
+ or "--demo" in sys.argv
26
+ ):
27
+ time.sleep(0.48)
28
+ elif (
29
+ (hasattr(sb_config, "slow_mode") and sb_config.slow_mode)
30
+ or "--slow" in sys.argv
31
+ ):
32
+ time.sleep(0.24)
25
33
 
26
34
  def __add_light_pause(self):
27
35
  time.sleep(0.007)
@@ -89,6 +97,10 @@ class CDPMethods():
89
97
  safe_url = False
90
98
  if not safe_url:
91
99
  time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)
100
+ if shared_utils.is_windows():
101
+ time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)
102
+ else:
103
+ time.sleep(0.012)
92
104
  self.__slow_mode_pause_if_set()
93
105
  self.loop.run_until_complete(self.page.wait())
94
106
 
@@ -248,6 +260,45 @@ class CDPMethods():
248
260
  def find_elements(self, selector, timeout=settings.SMALL_TIMEOUT):
249
261
  return self.select_all(selector, timeout=timeout)
250
262
 
263
+ def find_visible_elements(self, selector, timeout=settings.SMALL_TIMEOUT):
264
+ visible_elements = []
265
+ elements = self.select_all(selector, timeout=timeout)
266
+ for element in elements:
267
+ with suppress(Exception):
268
+ position = element.get_position()
269
+ if (position.width != 0 or position.height != 0):
270
+ visible_elements.append(element)
271
+ return visible_elements
272
+
273
+ def click_nth_element(self, selector, number):
274
+ elements = self.select_all(selector)
275
+ if len(elements) < number:
276
+ raise Exception(
277
+ "Not enough matching {%s} elements to "
278
+ "click number %s!" % (selector, number)
279
+ )
280
+ number = number - 1
281
+ if number < 0:
282
+ number = 0
283
+ element = elements[number]
284
+ element.click()
285
+
286
+ def click_nth_visible_element(self, selector, number):
287
+ """Finds all matching page elements and clicks the nth visible one.
288
+ Example: self.click_nth_visible_element('[type="checkbox"]', 5)
289
+ (Clicks the 5th visible checkbox on the page.)"""
290
+ elements = self.find_visible_elements(selector)
291
+ if len(elements) < number:
292
+ raise Exception(
293
+ "Not enough matching {%s} elements to "
294
+ "click number %s!" % (selector, number)
295
+ )
296
+ number = number - 1
297
+ if number < 0:
298
+ number = 0
299
+ element = elements[number]
300
+ element.click()
301
+
251
302
  def click_link(self, link_text):
252
303
  self.find_elements_by_text(link_text, "a")[0].click()
253
304
 
@@ -475,18 +526,36 @@ class CDPMethods():
475
526
  self.__slow_mode_pause_if_set()
476
527
  self.loop.run_until_complete(self.page.wait())
477
528
 
478
- def click_visible_elements(self, selector):
529
+ def click_visible_elements(self, selector, limit=0):
530
+ """Finds all matching page elements and clicks visible ones in order.
531
+ If a click reloads or opens a new page, the clicking will stop.
532
+ If no matching elements appear, an Exception will be raised.
533
+ If "limit" is set and > 0, will only click that many elements.
534
+ Also clicks elements that become visible from previous clicks.
535
+ Works best for actions such as clicking all checkboxes on a page.
536
+ Example: self.click_visible_elements('input[type="checkbox"]')"""
479
537
  elements = self.select_all(selector)
538
+ click_count = 0
480
539
  for element in elements:
540
+ if limit and limit > 0 and click_count >= limit:
541
+ return
481
542
  try:
482
- position = element.get_position()
483
- if (position.width != 0 or position.height != 0):
543
+ width = 0
544
+ height = 0
545
+ try:
546
+ position = element.get_position()
547
+ width = position.width
548
+ height = position.height
549
+ except Exception:
550
+ continue
551
+ if (width != 0 or height != 0):
484
552
  element.click()
485
- time.sleep(0.0375)
553
+ click_count += 1
554
+ time.sleep(0.042)
486
555
  self.__slow_mode_pause_if_set()
487
556
  self.loop.run_until_complete(self.page.wait())
488
557
  except Exception:
489
- pass
558
+ break
490
559
 
491
560
  def mouse_click(self, selector, timeout=settings.SMALL_TIMEOUT):
492
561
  """(Attempt simulating a mouse click)"""
@@ -578,9 +647,6 @@ class CDPMethods():
578
647
  with suppress(Exception):
579
648
  self.loop.run_until_complete(self.page.evaluate(js_code))
580
649
 
581
- def scroll_into_view(self, selector):
582
- self.find_element(selector).scroll_into_view()
583
-
584
650
  def send_keys(self, selector, text, timeout=settings.SMALL_TIMEOUT):
585
651
  self.__slow_mode_pause_if_set()
586
652
  element = self.select(selector, timeout=timeout)
@@ -602,10 +668,10 @@ class CDPMethods():
602
668
  text = text[:-1]
603
669
  for key in text:
604
670
  element.send_keys(key)
605
- time.sleep(0.0375)
671
+ time.sleep(0.042)
606
672
  if submit:
607
673
  element.send_keys("\r\n")
608
- time.sleep(0.0375)
674
+ time.sleep(0.042)
609
675
  self.__slow_mode_pause_if_set()
610
676
  self.loop.run_until_complete(self.page.wait())
611
677
 
@@ -675,7 +741,7 @@ class CDPMethods():
675
741
  return
676
742
  elif self.get_window()[1].window_state.value == "minimized":
677
743
  self.loop.run_until_complete(self.page.maximize())
678
- time.sleep(0.0375)
744
+ time.sleep(0.042)
679
745
  return self.loop.run_until_complete(self.page.maximize())
680
746
 
681
747
  def minimize(self):
@@ -685,7 +751,7 @@ class CDPMethods():
685
751
  def medimize(self):
686
752
  if self.get_window()[1].window_state.value == "minimized":
687
753
  self.loop.run_until_complete(self.page.medimize())
688
- time.sleep(0.0375)
754
+ time.sleep(0.042)
689
755
  return self.loop.run_until_complete(self.page.medimize())
690
756
 
691
757
  def set_window_rect(self, x, y, width, height):
@@ -694,7 +760,7 @@ class CDPMethods():
694
760
  self.page.set_window_size(
695
761
  left=x, top=y, width=width, height=height)
696
762
  )
697
- time.sleep(0.0375)
763
+ time.sleep(0.042)
698
764
  return self.loop.run_until_complete(
699
765
  self.page.set_window_size(
700
766
  left=x, top=y, width=width, height=height)
@@ -1059,7 +1125,7 @@ class CDPMethods():
1059
1125
  )
1060
1126
  with gui_lock:
1061
1127
  pyautogui.press(key)
1062
- time.sleep(0.0375)
1128
+ time.sleep(0.042)
1063
1129
  self.__slow_mode_pause_if_set()
1064
1130
  self.loop.run_until_complete(self.page.wait())
1065
1131
 
@@ -1073,7 +1139,7 @@ class CDPMethods():
1073
1139
  with gui_lock:
1074
1140
  for key in keys:
1075
1141
  pyautogui.press(key)
1076
- time.sleep(0.0375)
1142
+ time.sleep(0.042)
1077
1143
  self.__slow_mode_pause_if_set()
1078
1144
  self.loop.run_until_complete(self.page.wait())
1079
1145
 
@@ -1237,6 +1303,7 @@ class CDPMethods():
1237
1303
 
1238
1304
  def gui_drag_and_drop(self, drag_selector, drop_selector, timeframe=0.35):
1239
1305
  self.__slow_mode_pause_if_set()
1306
+ self.bring_active_window_to_front()
1240
1307
  x1, y1 = self.get_gui_element_center(drag_selector)
1241
1308
  self.__add_light_pause()
1242
1309
  x2, y2 = self.get_gui_element_center(drop_selector)
@@ -1326,10 +1393,14 @@ class CDPMethods():
1326
1393
 
1327
1394
  def gui_hover_element(self, selector, timeframe=0.25):
1328
1395
  self.__slow_mode_pause_if_set()
1329
- x, y = self.get_gui_element_center(selector)
1330
- self.__add_light_pause()
1331
- self.__gui_hover_x_y(x, y, timeframe=timeframe)
1332
- self.__slow_mode_pause_if_set()
1396
+ element_rect = self.get_gui_element_rect(selector)
1397
+ width = element_rect["width"]
1398
+ height = element_rect["height"]
1399
+ if width > 0 and height > 0:
1400
+ x, y = self.get_gui_element_center(selector)
1401
+ self.bring_active_window_to_front()
1402
+ self.__gui_hover_x_y(x, y, timeframe=timeframe)
1403
+ self.__slow_mode_pause_if_set()
1333
1404
  self.loop.run_until_complete(self.page.wait())
1334
1405
 
1335
1406
  def gui_hover_and_click(self, hover_selector, click_selector):
@@ -1337,6 +1408,7 @@ class CDPMethods():
1337
1408
  constants.MultiBrowser.PYAUTOGUILOCK
1338
1409
  )
1339
1410
  with gui_lock:
1411
+ self.bring_active_window_to_front()
1340
1412
  self.gui_hover_element(hover_selector)
1341
1413
  time.sleep(0.15)
1342
1414
  self.gui_hover_element(click_selector)
@@ -1408,25 +1480,53 @@ class CDPMethods():
1408
1480
  return True
1409
1481
  return False
1410
1482
 
1483
+ def wait_for_element_visible(
1484
+ self, selector, timeout=settings.SMALL_TIMEOUT
1485
+ ):
1486
+ try:
1487
+ self.select(selector, timeout=timeout)
1488
+ except Exception:
1489
+ raise Exception("Element {%s} was not found!" % selector)
1490
+ for i in range(30):
1491
+ if self.is_element_visible(selector):
1492
+ return self.select(selector)
1493
+ time.sleep(0.1)
1494
+ raise Exception("Element {%s} was not visible!" % selector)
1495
+
1411
1496
  def assert_element(self, selector, timeout=settings.SMALL_TIMEOUT):
1497
+ """Same as assert_element_visible()"""
1412
1498
  try:
1413
1499
  self.select(selector, timeout=timeout)
1414
1500
  except Exception:
1415
- raise Exception("Element {%s} not found!" % selector)
1501
+ raise Exception("Element {%s} was not found!" % selector)
1502
+ for i in range(30):
1503
+ if self.is_element_visible(selector):
1504
+ return True
1505
+ time.sleep(0.1)
1506
+ raise Exception("Element {%s} was not visible!" % selector)
1507
+
1508
+ def assert_element_visible(self, selector, timeout=settings.SMALL_TIMEOUT):
1509
+ """Same as assert_element()"""
1510
+ try:
1511
+ self.select(selector, timeout=timeout)
1512
+ except Exception:
1513
+ raise Exception("Element {%s} was not found!" % selector)
1416
1514
  for i in range(30):
1417
1515
  if self.is_element_visible(selector):
1418
1516
  return True
1419
1517
  time.sleep(0.1)
1420
- raise Exception("Element {%s} not visible!" % selector)
1518
+ raise Exception("Element {%s} was not visible!" % selector)
1421
1519
 
1422
1520
  def assert_element_present(self, selector, timeout=settings.SMALL_TIMEOUT):
1521
+ """Assert element is present in the DOM. (Visibility NOT required)"""
1423
1522
  try:
1424
1523
  self.select(selector, timeout=timeout)
1425
1524
  except Exception:
1426
- raise Exception("Element {%s} not found!" % selector)
1525
+ raise Exception("Element {%s} was not found!" % selector)
1427
1526
  return True
1428
1527
 
1429
1528
  def assert_element_absent(self, selector, timeout=settings.SMALL_TIMEOUT):
1529
+ """Assert element is not present in the DOM."""
1430
1530
  start_ms = time.time() * 1000.0
1431
1531
  stop_ms = start_ms + (timeout * 1000.0)
1432
1532
  for i in range(int(timeout * 10)):
@@ -1447,6 +1547,7 @@ class CDPMethods():
1447
1547
  def assert_element_not_visible(
1448
1548
  self, selector, timeout=settings.SMALL_TIMEOUT
1449
1549
  ):
1550
+ """Assert element is not visible on page. (May still be in DOM)"""
1450
1551
  start_ms = time.time() * 1000.0
1451
1552
  stop_ms = start_ms + (timeout * 1000.0)
1452
1553
  for i in range(int(timeout * 10)):
@@ -1466,6 +1567,21 @@ class CDPMethods():
1466
1567
  % (selector, timeout, plural)
1467
1568
  )
1468
1569
 
1570
+ def assert_element_attribute(self, selector, attribute, value=None):
1571
+ attributes = self.get_element_attributes(selector)
1572
+ if attribute not in attributes:
1573
+ raise Exception(
1574
+ "Attribute {%s} was not found in element {%s}!"
1575
+ % (attribute, selector)
1576
+ )
1577
+ if value and attributes[attribute] != value:
1578
+ raise Exception(
1579
+ "Expected value {%s} of attribute {%s} "
1580
+ "was not found in element {%s}! "
1581
+ "(Actual value was {%s})"
1582
+ % (value, attribute, selector, attributes[attribute])
1583
+ )
1584
+
1469
1585
  def assert_title(self, title):
1470
1586
  expected = title.strip()
1471
1587
  actual = self.get_title().strip()
@@ -1477,11 +1593,55 @@ class CDPMethods():
1477
1593
  raise Exception(error % (expected, actual))
1478
1594
  except Exception:
1479
1595
  time.sleep(2)
1480
- expected = title.strip()
1481
1596
  actual = self.get_title().strip()
1482
1597
  if expected != actual:
1483
1598
  raise Exception(error % (expected, actual))
1484
1599
 
1600
+ def assert_title_contains(self, substring):
1601
+ expected = substring.strip()
1602
+ actual = self.get_title().strip()
1603
+ error = (
1604
+ "Expected title substring [%s] does not appear "
1605
+ "in the actual page title [%s]!"
1606
+ )
1607
+ try:
1608
+ if expected not in actual:
1609
+ raise Exception(error % (expected, actual))
1610
+ except Exception:
1611
+ time.sleep(2)
1612
+ actual = self.get_title().strip()
1613
+ if expected not in actual:
1614
+ raise Exception(error % (expected, actual))
1615
+
1616
+ def assert_url(self, url):
1617
+ expected = url.strip()
1618
+ actual = self.get_current_url().strip()
1619
+ error = "Expected URL [%s] does not match the actual URL [%s]!"
1620
+ try:
1621
+ if expected != actual:
1622
+ raise Exception(error % (expected, actual))
1623
+ except Exception:
1624
+ time.sleep(2)
1625
+ actual = self.get_current_url().strip()
1626
+ if expected != actual:
1627
+ raise Exception(error % (expected, actual))
1628
+
1629
+ def assert_url_contains(self, substring):
1630
+ expected = substring.strip()
1631
+ actual = self.get_current_url().strip()
1632
+ error = (
1633
+ "Expected URL substring [%s] does not appear "
1634
+ "in the full URL [%s]!"
1635
+ )
1636
+ try:
1637
+ if expected not in actual:
1638
+ raise Exception(error % (expected, actual))
1639
+ except Exception:
1640
+ time.sleep(2)
1641
+ actual = self.get_current_url().strip()
1642
+ if expected not in actual:
1643
+ raise Exception(error % (expected, actual))
1644
+
1485
1645
  def assert_text(
1486
1646
  self, text, selector="html", timeout=settings.SMALL_TIMEOUT
1487
1647
  ):
@@ -1521,15 +1681,36 @@ class CDPMethods():
1521
1681
  % (text, element.text_all, selector)
1522
1682
  )
1523
1683
 
1524
- def scroll_down(self, amount=25):
1525
- self.loop.run_until_complete(
1526
- self.page.scroll_down(amount)
1527
- )
1684
+ def scroll_into_view(self, selector):
1685
+ self.find_element(selector).scroll_into_view()
1686
+ self.loop.run_until_complete(self.page.wait())
1687
+
1688
+ def scroll_to_y(self, y):
1689
+ y = int(y)
1690
+ js_code = "window.scrollTo(0, %s);" % y
1691
+ with suppress(Exception):
1692
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1693
+ self.loop.run_until_complete(self.page.wait())
1694
+
1695
+ def scroll_to_top(self):
1696
+ js_code = "window.scrollTo(0, 0);"
1697
+ with suppress(Exception):
1698
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1699
+ self.loop.run_until_complete(self.page.wait())
1700
+
1701
+ def scroll_to_bottom(self):
1702
+ js_code = "window.scrollTo(0, 10000);"
1703
+ with suppress(Exception):
1704
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1705
+ self.loop.run_until_complete(self.page.wait())
1528
1706
 
1529
1707
  def scroll_up(self, amount=25):
1530
- self.loop.run_until_complete(
1531
- self.page.scroll_up(amount)
1532
- )
1708
+ self.loop.run_until_complete(self.page.scroll_up(amount))
1709
+ self.loop.run_until_complete(self.page.wait())
1710
+
1711
+ def scroll_down(self, amount=25):
1712
+ self.loop.run_until_complete(self.page.scroll_down(amount))
1713
+ self.loop.run_until_complete(self.page.wait())
1533
1714
 
1534
1715
  def save_screenshot(self, name, folder=None, selector=None):
1535
1716
  filename = name
@@ -249,8 +249,11 @@ class DriverMethods():
249
249
  selector = kwargs["selector"]
250
250
  else:
251
251
  selector = args[0]
252
- self.driver.cdp.highlight(selector)
253
- return
252
+ if ":contains(" not in selector:
253
+ self.driver.cdp.highlight(selector)
254
+ return
255
+ else:
256
+ self.driver.connect()
254
257
  if "scroll" in kwargs:
255
258
  kwargs.pop("scroll")
256
259
  w_args = kwargs.copy()
@@ -1310,9 +1310,13 @@ class BaseCase(unittest.TestCase):
1310
1310
  return self.get_page_title()
1311
1311
 
1312
1312
  def get_user_agent(self):
1313
+ if self.__is_cdp_swap_needed():
1314
+ return self.cdp.get_user_agent()
1313
1315
  return self.execute_script("return navigator.userAgent;")
1314
1316
 
1315
1317
  def get_locale_code(self):
1318
+ if self.__is_cdp_swap_needed():
1319
+ return self.cdp.get_locale_code()
1316
1320
  return self.execute_script(
1317
1321
  "return navigator.language || navigator.languages[0];"
1318
1322
  )
@@ -2166,7 +2170,6 @@ class BaseCase(unittest.TestCase):
2166
2170
  if limit and limit > 0 and len(elements) > limit:
2167
2171
  elements = elements[:limit]
2168
2172
  return elements
2169
-
2170
2173
  self.wait_for_ready_state_complete()
2171
2174
  time.sleep(0.05)
2172
2175
  elements = self.driver.find_elements(by=by, value=selector)
@@ -2178,6 +2181,11 @@ class BaseCase(unittest.TestCase):
2178
2181
  """Returns a list of matching WebElements that are visible.
2179
2182
  If "limit" is set and > 0, will only return that many elements."""
2180
2183
  selector, by = self.__recalculate_selector(selector, by)
2184
+ if self.__is_cdp_swap_needed():
2185
+ elements = self.cdp.find_visible_elements(selector)
2186
+ if limit and limit > 0 and len(elements) > limit:
2187
+ elements = elements[:limit]
2188
+ return elements
2181
2189
  self.wait_for_ready_state_complete()
2182
2190
  time.sleep(0.05)
2183
2191
  return page_actions.find_visible_elements(
@@ -2201,7 +2209,7 @@ class BaseCase(unittest.TestCase):
2201
2209
  timeout = self.__get_new_timeout(timeout)
2202
2210
  selector, by = self.__recalculate_selector(selector, by)
2203
2211
  if self.__is_cdp_swap_needed():
2204
- self.cdp.click_visible_elements(selector)
2212
+ self.cdp.click_visible_elements(selector, limit)
2205
2213
  return
2206
2214
  self.wait_for_ready_state_complete()
2207
2215
  if self.__needs_minimum_wait():
@@ -2283,13 +2291,16 @@ class BaseCase(unittest.TestCase):
2283
2291
  ):
2284
2292
  """Finds all matching page elements and clicks the nth visible one.
2285
2293
  Example: self.click_nth_visible_element('[type="checkbox"]', 5)
2286
- (Clicks the 5th visible checkbox on the page.)"""
2294
+ (Clicks the 5th visible checkbox on the page.)"""
2287
2295
  self.__check_scope()
2288
2296
  if not timeout:
2289
2297
  timeout = settings.SMALL_TIMEOUT
2290
2298
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
2291
2299
  timeout = self.__get_new_timeout(timeout)
2292
2300
  selector, by = self.__recalculate_selector(selector, by)
2301
+ if self.__is_cdp_swap_needed():
2302
+ self.cdp.click_nth_visible_element(selector, number)
2303
+ return
2293
2304
  self.wait_for_ready_state_complete()
2294
2305
  self.wait_for_element_present(selector, by=by, timeout=timeout)
2295
2306
  elements = self.find_visible_elements(selector, by=by)
@@ -2897,6 +2908,9 @@ class BaseCase(unittest.TestCase):
2897
2908
  drop_selector, drop_by = self.__recalculate_selector(
2898
2909
  drop_selector, drop_by
2899
2910
  )
2911
+ if self.__is_cdp_swap_needed():
2912
+ self.cdp.gui_drag_and_drop(drag_selector, drop_selector)
2913
+ return
2900
2914
  drag_element = self.wait_for_element_clickable(
2901
2915
  drag_selector, by=drag_by, timeout=timeout
2902
2916
  )
@@ -4537,6 +4551,11 @@ class BaseCase(unittest.TestCase):
4537
4551
  def get_cookies(self):
4538
4552
  return self.driver.get_cookies()
4539
4553
 
4554
+ def get_cookie_string(self):
4555
+ if self.__is_cdp_swap_needed():
4556
+ return self.cdp.get_cookie_string()
4557
+ return self.execute_script("return document.cookie;")
4558
+
4540
4559
  def add_cookie(self, cookie_dict, expiry=False):
4541
4560
  """Usage examples:
4542
4561
  self.add_cookie({'name': 'foo', 'value': 'bar'})
@@ -5419,7 +5438,10 @@ class BaseCase(unittest.TestCase):
5419
5438
  new_file = True
5420
5439
  sb_config._recorded_actions[filename] = []
5421
5440
  data.append("from seleniumbase import BaseCase")
5422
- data.append("BaseCase.main(__name__, __file__)")
5441
+ if "--uc" in sys.argv:
5442
+ data.append('BaseCase.main(__name__, __file__, "--uc")')
5443
+ else:
5444
+ data.append("BaseCase.main(__name__, __file__)")
5423
5445
  data.append("")
5424
5446
  data.append("")
5425
5447
  data.append("class %s(BaseCase):" % classname)
@@ -5429,7 +5451,13 @@ class BaseCase(unittest.TestCase):
5429
5451
  data.append("class %s(BaseCase):" % classname)
5430
5452
  data.append(" def %s(self):" % methodname)
5431
5453
  if len(sb_actions) > 0:
5454
+ if "--uc" in sys.argv:
5455
+ data.append(" self.activate_cdp_mode()")
5432
5456
  for action in sb_actions:
5457
+ if "--uc" in sys.argv:
5458
+ action = action.replace(
5459
+ "self.type(", "self.press_keys("
5460
+ )
5433
5461
  data.append(" " + action)
5434
5462
  else:
5435
5463
  data.append(" pass")
@@ -5607,6 +5635,9 @@ class BaseCase(unittest.TestCase):
5607
5635
  data.append(" Scenario: %s" % scenario_test)
5608
5636
  if len(behave_actions) > 0:
5609
5637
  count = 0
5638
+ if "--uc" in sys.argv:
5639
+ data.append(" Given Activate CDP Mode")
5640
+ count += 1
5610
5641
  for action in behave_actions:
5611
5642
  if count == 0:
5612
5643
  data.append(" Given " + action)
@@ -6144,6 +6175,9 @@ class BaseCase(unittest.TestCase):
6144
6175
  original_selector = selector
6145
6176
  original_by = by
6146
6177
  selector, by = self.__recalculate_selector(selector, by)
6178
+ if self.__is_cdp_swap_needed() and ":contains(" not in selector:
6179
+ self.cdp.scroll_into_view(selector)
6180
+ return
6147
6181
  element = self.wait_for_element_visible(
6148
6182
  original_selector, by=original_by, timeout=timeout
6149
6183
  )
@@ -6190,24 +6224,36 @@ class BaseCase(unittest.TestCase):
6190
6224
  def scroll_to_top(self):
6191
6225
  """Scroll to the top of the page."""
6192
6226
  self.__check_scope()
6227
+ if self.__is_cdp_swap_needed():
6228
+ self.cdp.scroll_to_top()
6229
+ return
6193
6230
  scroll_script = "window.scrollTo(0, 0);"
6194
- try:
6231
+ with suppress(Exception):
6195
6232
  self.execute_script(scroll_script)
6196
6233
  time.sleep(0.012)
6197
- return True
6198
- except Exception:
6199
- return False
6200
6234
 
6201
6235
  def scroll_to_bottom(self):
6202
6236
  """Scroll to the bottom of the page."""
6203
6237
  self.__check_scope()
6238
+ if self.__is_cdp_swap_needed():
6239
+ self.cdp.scroll_to_bottom()
6240
+ return
6204
6241
  scroll_script = "window.scrollTo(0, 10000);"
6205
- try:
6242
+ with suppress(Exception):
6243
+ self.execute_script(scroll_script)
6244
+ time.sleep(0.012)
6245
+
6246
+ def scroll_to_y(self, y):
6247
+ """Scroll to y position on the page."""
6248
+ self.__check_scope()
6249
+ y = int(y)
6250
+ if self.__is_cdp_swap_needed():
6251
+ self.cdp.scroll_to_y(y)
6252
+ return
6253
+ scroll_script = "window.scrollTo(0, %s);" % y
6254
+ with suppress(Exception):
6206
6255
  self.execute_script(scroll_script)
6207
6256
  time.sleep(0.012)
6208
- return True
6209
- except Exception:
6210
- return False
6211
6257
 
6212
6258
  def click_xpath(self, xpath):
6213
6259
  """Technically, self.click() automatically detects xpath selectors,
@@ -7620,7 +7666,10 @@ class BaseCase(unittest.TestCase):
7620
7666
  if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
7621
7667
  timeout = self.__get_new_timeout(timeout)
7622
7668
  selector, by = self.__recalculate_selector(selector, by)
7623
- if self.__is_shadow_selector(selector):
7669
+ if self.__is_cdp_swap_needed():
7670
+ self.cdp.assert_element_attribute(selector, attribute, value)
7671
+ return
7672
+ elif self.__is_shadow_selector(selector):
7624
7673
  return self.__wait_for_shadow_attribute_present(
7625
7674
  selector, attribute, value=value, timeout=timeout
7626
7675
  )
@@ -7645,6 +7694,9 @@ class BaseCase(unittest.TestCase):
7645
7694
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
7646
7695
  timeout = self.__get_new_timeout(timeout)
7647
7696
  selector, by = self.__recalculate_selector(selector, by)
7697
+ if self.__is_cdp_swap_needed():
7698
+ self.cdp.assert_element_attribute(selector, attribute, value)
7699
+ return
7648
7700
  self.wait_for_attribute(
7649
7701
  selector, attribute, value=value, by=by, timeout=timeout
7650
7702
  )
@@ -7743,6 +7795,9 @@ class BaseCase(unittest.TestCase):
7743
7795
  but then the title switches over to the actual page title.
7744
7796
  In Recorder Mode, this assertion is skipped because the Recorder
7745
7797
  changes the page title to the selector of the hovered element."""
7798
+ if self.__is_cdp_swap_needed():
7799
+ self.cdp.assert_title_contains(substring)
7800
+ return
7746
7801
  self.wait_for_ready_state_complete()
7747
7802
  expected = substring.strip()
7748
7803
  actual = self.get_page_title().strip()
@@ -7786,6 +7841,9 @@ class BaseCase(unittest.TestCase):
7786
7841
 
7787
7842
  def assert_url(self, url):
7788
7843
  """Asserts that the web page URL matches the expected URL."""
7844
+ if self.__is_cdp_swap_needed():
7845
+ self.cdp.assert_url(url)
7846
+ return
7789
7847
  self.wait_for_ready_state_complete()
7790
7848
  expected = url.strip()
7791
7849
  actual = self.get_current_url().strip()
@@ -7821,6 +7879,9 @@ class BaseCase(unittest.TestCase):
7821
7879
 
7822
7880
  def assert_url_contains(self, substring):
7823
7881
  """Asserts that the URL substring appears in the full URL."""
7882
+ if self.__is_cdp_swap_needed():
7883
+ self.cdp.assert_url_contains(substring)
7884
+ return
7824
7885
  self.wait_for_ready_state_complete()
7825
7886
  expected = substring.strip()
7826
7887
  actual = self.get_current_url().strip()
@@ -8019,6 +8080,8 @@ class BaseCase(unittest.TestCase):
8019
8080
 
8020
8081
  def is_online(self):
8021
8082
  """Return True if connected to the Internet."""
8083
+ if self.__is_cdp_swap_needed():
8084
+ return self.cdp.evaluate("navigator.onLine;")
8022
8085
  return self.execute_script("return navigator.onLine;")
8023
8086
 
8024
8087
  def is_connected(self):
@@ -8927,7 +8990,9 @@ class BaseCase(unittest.TestCase):
8927
8990
  timeout = self.__get_new_timeout(timeout)
8928
8991
  original_selector = selector
8929
8992
  selector, by = self.__recalculate_selector(selector, by)
8930
- if self.__is_shadow_selector(selector):
8993
+ if self.__is_cdp_swap_needed():
8994
+ return self.cdp.select(selector)
8995
+ elif self.__is_shadow_selector(selector):
8931
8996
  # If a shadow selector, use visible instead of clickable
8932
8997
  return self.__wait_for_shadow_element_visible(selector, timeout)
8933
8998
  return page_actions.wait_for_element_clickable(
@@ -9328,7 +9393,7 @@ class BaseCase(unittest.TestCase):
9328
9393
  selector, by = self.__recalculate_selector(selector, by)
9329
9394
  if self.__is_cdp_swap_needed():
9330
9395
  return self.cdp.select(selector)
9331
- if self.__is_shadow_selector(selector):
9396
+ elif self.__is_shadow_selector(selector):
9332
9397
  return self.__wait_for_shadow_element_present(selector, timeout)
9333
9398
  return page_actions.wait_for_element_present(
9334
9399
  self.driver,
@@ -9391,6 +9456,8 @@ class BaseCase(unittest.TestCase):
9391
9456
  if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
9392
9457
  timeout = self.__get_new_timeout(timeout)
9393
9458
  css_selector = self.convert_to_css_selector(selector, by=by)
9459
+ if self.__is_cdp_swap_needed():
9460
+ return self.cdp.select(css_selector)
9394
9461
  return js_utils.wait_for_css_query_selector(
9395
9462
  self.driver, css_selector, timeout
9396
9463
  )
@@ -9612,7 +9679,7 @@ class BaseCase(unittest.TestCase):
9612
9679
  selector, by = self.__recalculate_selector(selector, by)
9613
9680
  if self.__is_cdp_swap_needed():
9614
9681
  return self.cdp.find_element(selector)
9615
- if self.__is_shadow_selector(selector):
9682
+ elif self.__is_shadow_selector(selector):
9616
9683
  return self.__wait_for_shadow_text_visible(text, selector, timeout)
9617
9684
  return page_actions.wait_for_text_visible(
9618
9685
  self.driver, text, selector, by, timeout
@@ -10135,6 +10202,9 @@ class BaseCase(unittest.TestCase):
10135
10202
  timeout = settings.SMALL_TIMEOUT
10136
10203
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
10137
10204
  timeout = self.__get_new_timeout(timeout)
10205
+ if self.__is_cdp_swap_needed():
10206
+ self.cdp.assert_element_not_visible(selector)
10207
+ return True
10138
10208
  self.wait_for_element_not_visible(selector, by=by, timeout=timeout)
10139
10209
  if self.recorder_mode and self.__current_url_is_recordable():
10140
10210
  if self.get_session_storage_item("pause_recorder") == "no":
@@ -14471,7 +14541,7 @@ class BaseCase(unittest.TestCase):
14471
14541
  message = (
14472
14542
  "Expected value {%s} for attribute {%s} of element "
14473
14543
  "{%s} was not present after %s second%s! "
14474
- "(The actual value was {%s})"
14544
+ "(Actual value was {%s})"
14475
14545
  % (
14476
14546
  value,
14477
14547
  attribute,
@@ -15420,7 +15490,8 @@ class BaseCase(unittest.TestCase):
15420
15490
  elif hasattr(self, "_using_sb_fixture") and self._using_sb_fixture:
15421
15491
  test_id = sb_config._latest_display_id
15422
15492
  test_id = test_id.replace(".py::", ".").replace("::", ".")
15423
- test_id = test_id.replace("/", ".").replace(" ", "_")
15493
+ test_id = test_id.replace("/", ".").replace("\\", ".")
15494
+ test_id = test_id.replace(" ", "_")
15424
15495
  # Linux filename length limit for `codecs.open(filename)` = 255
15425
15496
  # 255 - len("latest_logs/") - len("/basic_test_info.txt") = 223
15426
15497
  if len(test_id) <= 223:
@@ -16117,11 +16188,7 @@ class BaseCase(unittest.TestCase):
16117
16188
  # This test already called tearDown()
16118
16189
  return
16119
16190
  if hasattr(self, "recorder_mode") and self.recorder_mode:
16120
- if self.undetectable:
16121
- try:
16122
- self.driver.window_handles
16123
- except Exception:
16124
- self.driver.connect()
16191
+ page_actions._reconnect_if_disconnected(self.driver)
16125
16192
  try:
16126
16193
  self.__process_recorded_actions()
16127
16194
  except Exception as e:
@@ -16162,12 +16229,7 @@ class BaseCase(unittest.TestCase):
16162
16229
  )
16163
16230
  raise Exception(message)
16164
16231
  # *** Start tearDown() officially ***
16165
- if self.undetectable:
16166
- try:
16167
- self.driver.window_handles
16168
- except Exception:
16169
- with suppress(Exception):
16170
- self.driver.connect()
16232
+ page_actions._reconnect_if_disconnected(self.driver)
16171
16233
  self.__slow_mode_pause_if_active()
16172
16234
  has_exception = self.__has_exception()
16173
16235
  sb_config._has_exception = has_exception
@@ -376,6 +376,7 @@ class Mobile:
376
376
  class UC:
377
377
  RECONNECT_TIME = 2.4 # Seconds
378
378
  CDP_MODE_OPEN_WAIT = 0.9 # Seconds
379
+ EXTRA_WINDOWS_WAIT = 0.3 # Seconds
379
380
 
380
381
 
381
382
  class ValidBrowsers:
@@ -10,6 +10,7 @@ from seleniumbase import config as sb_config
10
10
  from seleniumbase.config import settings
11
11
  from seleniumbase.fixtures import constants
12
12
  from seleniumbase.fixtures import css_to_xpath
13
+ from seleniumbase.fixtures import shared_utils
13
14
  from seleniumbase.fixtures import xpath_to_css
14
15
 
15
16
 
@@ -24,9 +25,6 @@ def wait_for_ready_state_complete(driver, timeout=settings.LARGE_TIMEOUT):
24
25
  (Previously, tests would fail immediately if exceeding the timeout.)"""
25
26
  if hasattr(settings, "SKIP_JS_WAITS") and settings.SKIP_JS_WAITS:
26
27
  return
27
- if sb_config.time_limit and not sb_config.recorder_mode:
28
- from seleniumbase.fixtures import shared_utils
29
-
30
28
  start_ms = time.time() * 1000.0
31
29
  stop_ms = start_ms + (timeout * 1000.0)
32
30
  for x in range(int(timeout * 10)):
@@ -243,8 +241,6 @@ def escape_quotes_if_needed(string):
243
241
  def is_in_frame(driver):
244
242
  # Returns True if the driver has switched to a frame.
245
243
  # Returns False if the driver was on default content.
246
- from seleniumbase.fixtures import shared_utils
247
-
248
244
  if shared_utils.is_cdp_swap_needed(driver):
249
245
  return False
250
246
  in_basic_frame = driver.execute_script(
@@ -1555,8 +1555,6 @@ def _reconnect_if_disconnected(driver):
1555
1555
  if (
1556
1556
  hasattr(driver, "_is_using_uc")
1557
1557
  and driver._is_using_uc
1558
- and hasattr(driver, "_is_connected")
1559
- and not driver._is_connected
1560
1558
  and hasattr(driver, "is_connected")
1561
1559
  and not driver.is_connected()
1562
1560
  ):
@@ -84,6 +84,17 @@ def fix_url_as_needed(url):
84
84
  return url
85
85
 
86
86
 
87
+ def reconnect_if_disconnected(driver):
88
+ if (
89
+ hasattr(driver, "_is_using_uc")
90
+ and driver._is_using_uc
91
+ and hasattr(driver, "is_connected")
92
+ and not driver.is_connected()
93
+ ):
94
+ with suppress(Exception):
95
+ driver.connect()
96
+
97
+
87
98
  def is_cdp_swap_needed(driver):
88
99
  """
89
100
  When someone is using CDP Mode with a disconnected webdriver,
@@ -1202,7 +1202,7 @@ def SB(
1202
1202
  from seleniumbase.core import download_helper
1203
1203
  from seleniumbase.core import proxy_helper
1204
1204
 
1205
- log_helper.log_folder_setup(constants.Logs.LATEST + "/")
1205
+ log_helper.log_folder_setup(constants.Logs.LATEST + os.sep)
1206
1206
  log_helper.clear_empty_logs()
1207
1207
  download_helper.reset_downloads_folder()
1208
1208
  if not sb_config.multi_proxy:
@@ -1228,7 +1228,7 @@ def SB(
1228
1228
  the_traceback = traceback.format_exc().strip()
1229
1229
  try:
1230
1230
  p2 = the_traceback.split(', in ')[1].split('", line ')[0]
1231
- filename = p2.split("/")[-1]
1231
+ filename = p2.split(os.sep)[-1]
1232
1232
  sb.cm_filename = filename
1233
1233
  except Exception:
1234
1234
  sb.cm_filename = None
@@ -455,7 +455,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
455
455
  if self.service.is_connectable():
456
456
  self.stop_client()
457
457
  self.service.stop()
458
- self._is_connected = False
458
+ self._is_connected = False
459
459
 
460
460
  def connect(self):
461
461
  """Starts the chromedriver service that runs in the background
@@ -211,7 +211,11 @@ async def start(
211
211
  expert=expert,
212
212
  **kwargs,
213
213
  )
214
- return await Browser.create(config)
214
+ try:
215
+ return await Browser.create(config)
216
+ except Exception:
217
+ time.sleep(0.15)
218
+ return await Browser.create(config)
215
219
 
216
220
 
217
221
  async def start_async(*args, **kwargs) -> Browser:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: seleniumbase
3
- Version: 4.32.10a3
3
+ Version: 4.32.12
4
4
  Summary: A complete web automation framework for end-to-end testing.
5
5
  Home-page: https://github.com/seleniumbase/SeleniumBase
6
6
  Author: Michael Mintz
@@ -59,7 +59,7 @@ Classifier: Topic :: Utilities
59
59
  Requires-Python: >=3.8
60
60
  Description-Content-Type: text/markdown
61
61
  License-File: LICENSE
62
- Requires-Dist: pip>=24.2
62
+ Requires-Dist: pip>=24.3.1
63
63
  Requires-Dist: packaging>=24.2
64
64
  Requires-Dist: wheel>=0.45.0
65
65
  Requires-Dist: attrs>=24.2.0
@@ -127,7 +127,7 @@ Requires-Dist: allure-behave>=2.13.5; extra == "allure"
127
127
  Provides-Extra: coverage
128
128
  Requires-Dist: coverage>=7.6.1; python_version < "3.9" and extra == "coverage"
129
129
  Requires-Dist: pytest-cov>=5.0.0; python_version < "3.9" and extra == "coverage"
130
- Requires-Dist: coverage>=7.6.4; python_version >= "3.9" and extra == "coverage"
130
+ Requires-Dist: coverage>=7.6.7; python_version >= "3.9" and extra == "coverage"
131
131
  Requires-Dist: pytest-cov>=6.0.0; python_version >= "3.9" and extra == "coverage"
132
132
  Provides-Extra: flake8
133
133
  Requires-Dist: mccabe==0.7.0; extra == "flake8"
@@ -1,15 +1,15 @@
1
1
  sbase/__init__.py,sha256=02izDj786GVBT0bpSq2Q2O8uwSxtyT09pnobZz91ML8,605
2
2
  sbase/__main__.py,sha256=G0bVB1-DM4PGwQ1KyOupaWCs4ePbChZNNWuX2htim5U,647
3
- sbase/steps.py,sha256=bKT_u5bJkKzYWEuAXi9NVVRYYxQRCM1_YJUrNFFRVPY,42865
3
+ sbase/steps.py,sha256=_WvAjydKqZfTdnZW9LPKkRty-g-lfdUPmLqnZj6ulcs,43013
4
4
  seleniumbase/__init__.py,sha256=OtJh8nGKL4xtZpw8KPqmn7Q6R-86t4cWUDyVF5MbMTo,2398
5
5
  seleniumbase/__main__.py,sha256=dn1p6dgCchmcH1zzTzzQvFwwdQQqnTGH6ULV9m4hv24,654
6
- seleniumbase/__version__.py,sha256=14fllQeteOT_Suz0vgP14dq-Lmrkoig_p-X-_kTOUzg,49
6
+ seleniumbase/__version__.py,sha256=8RXHuvmxJAt6kjpfvDeMQuCQ2ZFJUK225_rPJF4FZ3I,47
7
7
  seleniumbase/behave/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  seleniumbase/behave/behave_helper.py,sha256=elkl8P9eLulRAioLstE9baYNM9N_PHBmAOcajX-pH_Y,24198
9
9
  seleniumbase/behave/behave_sb.py,sha256=-hza7Nx2U41mSObYiPMi48v3JlPh3sJO3yzP0kqZ1Gk,59174
10
10
  seleniumbase/behave/steps.py,sha256=8-N-NB2tnDsxaP4LSg-uSBgbwZYMS6ZEL1oggO1PCoU,390
11
11
  seleniumbase/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- seleniumbase/common/decorators.py,sha256=xDW2tlXKOV-3xN8h7hP6iRleenn17tAM1ToPZ-eGQ3Y,7653
12
+ seleniumbase/common/decorators.py,sha256=DYdjtF8Y7YJj0JS33WtmsIn4yddPoniNwcPyrgt522o,7857
13
13
  seleniumbase/common/encryption.py,sha256=IfAuE0hdrd3y-_qAQUWdSOKRGQ4QN24uTOBiQ9Eeq2s,5593
14
14
  seleniumbase/common/exceptions.py,sha256=SbdHOTbg7ycrioWleAVw9kG9vBtDfbYoj3e7ikkYYz0,3257
15
15
  seleniumbase/common/obfuscate.py,sha256=VrwPbVigPH_Jk6ADCk5_miMoq1VK4M9SKnYScDkk-DQ,1204
@@ -30,13 +30,13 @@ seleniumbase/console_scripts/sb_mkchart.py,sha256=ep9g-9CSIwaOJKVxhB3xjRQpfsuApy
30
30
  seleniumbase/console_scripts/sb_mkdir.py,sha256=csqyWGEUdT2slLnQU3p9gnu5qE26TSVi_ZE3wttH_SQ,29778
31
31
  seleniumbase/console_scripts/sb_mkfile.py,sha256=OWYd4yFccmjrd-gNn1t1una-HDRU2_N2-r4Tg3nHsj0,17744
32
32
  seleniumbase/console_scripts/sb_mkpres.py,sha256=EWFRVacjYTX49y-fEiYTZacM9_01IxuuaO4nMjHrIGo,11015
33
- seleniumbase/console_scripts/sb_mkrec.py,sha256=WrpT4Qd4TWAU3X5CMm_IBb4FhL27FISU9GD2Spe7svY,11927
33
+ seleniumbase/console_scripts/sb_mkrec.py,sha256=PrizjTmyrROYPO0yDm-zQS3QSfsZNeAmcJKKUvfgLhc,11966
34
34
  seleniumbase/console_scripts/sb_objectify.py,sha256=nGxtVGL_nHj0bLHvk86znV3EABsE2_tjF74lgto1_Mk,122020
35
35
  seleniumbase/console_scripts/sb_print.py,sha256=tNy-bMDgwHJO3bZxMpmo9weSE8uhbH0CUpP4VZqWxvI,30558
36
- seleniumbase/console_scripts/sb_recorder.py,sha256=1oAA4wFzVboNhIFDwJLD3jgy9RuoavywKQG7R67bNZE,10908
36
+ seleniumbase/console_scripts/sb_recorder.py,sha256=fnHb5-kh11Hit-E9Ha-e4QXzqLcZvtij6mb5qNd4B1Q,11032
37
37
  seleniumbase/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  seleniumbase/core/application_manager.py,sha256=e_0sjtI8cjY5BNyZj1QBR0j6_oCScxGmSXYEpcYwuZE,576
39
- seleniumbase/core/browser_launcher.py,sha256=spXqxLljEovT48O0zbKWuTfG1OS4y9VjYKI8pufQIkk,219268
39
+ seleniumbase/core/browser_launcher.py,sha256=Jr4MM0CCXqrwlZalpr-N89D5XKtWPOPS3g9rnHm5j-s,220223
40
40
  seleniumbase/core/capabilities_parser.py,sha256=meIS2uHapTCq2ldfNAToC7r0cKmZDRXuYNKExM1GHDY,6038
41
41
  seleniumbase/core/colored_traceback.py,sha256=DrRWfg7XEnKcgY59Xj7Jdk09H-XqHYBSUpB-DiZt6iY,2020
42
42
  seleniumbase/core/create_db_tables.sql,sha256=VWPtrdiW_HQ6yETHjqTu-VIrTwvd8I8o1NfBeaVSHpU,972
@@ -44,14 +44,14 @@ seleniumbase/core/detect_b_ver.py,sha256=RxeGRMbBUTMrXh5KsS1P1SH7eEKYbzL1vQw1gTd
44
44
  seleniumbase/core/download_helper.py,sha256=qSR54kQISucF4RQaLCOuuerSu6DR41juGi_30HVvWYY,2943
45
45
  seleniumbase/core/encoded_images.py,sha256=rDKJ4cNJSuKiRcFViYU7bjyTS9_moI57gUPRXVg3u2k,14209
46
46
  seleniumbase/core/jqc_helper.py,sha256=2DDQr9Q2jSSZqFzX588jLlUM9oJvyrRWq2aORSIPUdI,10322
47
- seleniumbase/core/log_helper.py,sha256=c4gWI_spHF0N9co2W0-eR_s3y20v9FrQH8c9Vb76BXU,22593
47
+ seleniumbase/core/log_helper.py,sha256=T_nPPSRSjhqt6TTzVE6pN1owVvIeRBNF5pWK6sl4dMY,22609
48
48
  seleniumbase/core/mysql.py,sha256=8Fzj3p5dhtDWfMpFqFYxpSwa9s1UltiHsWJ56_aPOqk,3993
49
49
  seleniumbase/core/proxy_helper.py,sha256=cXhu8ErK9Vjdm82RMaQj7hEq_yUWizSp6LyiD50Ieu4,8020
50
50
  seleniumbase/core/recorder_helper.py,sha256=fNGjbapXmEsht54x1o6Igk198QdnPxDDnjUOzQxNhNQ,25055
51
51
  seleniumbase/core/report_helper.py,sha256=AIl6Qava2yW1uSzbLpJBlPlYDz0KE-rVhogh8hsGWBo,12201
52
52
  seleniumbase/core/s3_manager.py,sha256=bkeI8I4y19ebWuQG1oEZV5qJbotC6eN8vin31OCNWJk,3521
53
- seleniumbase/core/sb_cdp.py,sha256=CPQvyJ8v_xozVdGt5g9JFjo0aYsLaOEm9gnthpysrOc,59586
54
- seleniumbase/core/sb_driver.py,sha256=Yd9BpjnOUuZqYRGmsERVKc1W-e8qwTt7SAytLatkXPA,12070
53
+ seleniumbase/core/sb_cdp.py,sha256=hYnEXSTB_nM-rJSsLkPUbE2Di1ij_2Kb-Fbmso3zC4g,66948
54
+ seleniumbase/core/sb_driver.py,sha256=xMgtvBitEMr73RkqgQLT-3IvkP6sh7cJv-caCUPoO-o,12179
55
55
  seleniumbase/core/session_helper.py,sha256=s9zD3PVZEWVzG2h81cCUskbNWLfdjC_LwwQjKptHCak,558
56
56
  seleniumbase/core/settings_parser.py,sha256=KokVXpCiGZhJ-D4Bo-hizPz5r-iefzWoiTANu9zNaq4,7504
57
57
  seleniumbase/core/style_sheet.py,sha256=tPpJ1xl6Kuw425Z-3Y0OgVRLGXk_ciBDREZjXk_NuPE,11395
@@ -65,14 +65,14 @@ seleniumbase/extensions/disable_csp.zip,sha256=YMifIIgEBiLrEFrS1sfW4Exh4br1V4oK1
65
65
  seleniumbase/extensions/recorder.zip,sha256=OOyzF-Ize2cSRu1CqhzSAq5vusI9hqLLd2OIApUHesI,11918
66
66
  seleniumbase/extensions/sbase_ext.zip,sha256=3s1N8zrVaMz8RQEOIoBzC3KDjtmHwVZRvVsX25Odr_s,8175
67
67
  seleniumbase/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- seleniumbase/fixtures/base_case.py,sha256=ad--jkCZk2CTzXXk4_ncrI8nqWgHhGPjVmS3vHhnplQ,712849
69
- seleniumbase/fixtures/constants.py,sha256=XYYMpB-ZDI756LvfhSK9RxdqQ_qO9fJVXgBqBYlQNkk,13609
68
+ seleniumbase/fixtures/base_case.py,sha256=Pz5y5R3EeOYiUgK1Zutc8QYlQI7Lb3_uzGAGBhuQi5g,715596
69
+ seleniumbase/fixtures/constants.py,sha256=e1LppavlrAcI4XBJMq7u5j8SffaQ7SPQps1y0YvZYfY,13649
70
70
  seleniumbase/fixtures/css_to_xpath.py,sha256=9ouDB1xl4MJ2os6JOgTIAyHKOQfuxtxvXC3O5hSnEKA,1954
71
71
  seleniumbase/fixtures/errors.py,sha256=KyxuEVx_e3MPhVrJfNIa_3ltMpbCFxfy_jxK8RFNTns,555
72
- seleniumbase/fixtures/js_utils.py,sha256=5o4CTLcCyd717lJ_atOYcC6kPRiZFx-LJIlixRrP_cE,51061
73
- seleniumbase/fixtures/page_actions.py,sha256=fOCb2NB2PpEaE8gpAVu-73VjwLzfwP1R9HsRkix_z6s,66634
72
+ seleniumbase/fixtures/js_utils.py,sha256=2l4UY_zUBsMviRoJpLA0z3XxhGeLMWj0Mv07FUR_jzg,50939
73
+ seleniumbase/fixtures/page_actions.py,sha256=dbp63c-7asYZyd8aOu57Y3dxQQozp_VJsP5h74s1kBA,66552
74
74
  seleniumbase/fixtures/page_utils.py,sha256=5m7iXpikLs80TJoRO6_gEfXE1AKeQgcH1aFbR8o1C9A,12034
75
- seleniumbase/fixtures/shared_utils.py,sha256=MwCrt58YAv_XrfNfYjo_quUTWfYArlnf-aexSZGIyRs,7271
75
+ seleniumbase/fixtures/shared_utils.py,sha256=WbPb15IvIWzRtMInKG8DUzJ26UZU-PixdOwTCjXQirU,7545
76
76
  seleniumbase/fixtures/unittest_helper.py,sha256=sfZ92rZeBAn_sF_yQ3I6_I7h3lyU5-cV_UMegBNoEm8,1294
77
77
  seleniumbase/fixtures/words.py,sha256=FOA4mAYvl3EPVpBTvgvK6YwCL8BdlRCmed685kEe7Vg,7827
78
78
  seleniumbase/fixtures/xpath_to_css.py,sha256=lML56k656fElXJ4NJF07r35FjctrbgQkXUotNk7A-as,8876
@@ -90,7 +90,7 @@ seleniumbase/plugins/driver_manager.py,sha256=s20s0pJYaNrG0WNwyIC04oUMRVFjtm6V_n
90
90
  seleniumbase/plugins/page_source.py,sha256=loTnXxOj4kxEukuTZEiGyvKBhY3KDVDMnNlHHheTBDE,1889
91
91
  seleniumbase/plugins/pytest_plugin.py,sha256=Up96HY6q3hcPo4LQoEcKqt1hm2OmY5GZz_nMXTqDSXQ,97185
92
92
  seleniumbase/plugins/s3_logging_plugin.py,sha256=WDfertQgGOW_SRJpFMaekYD6vBVW9VO62POtXXy2HCM,2319
93
- seleniumbase/plugins/sb_manager.py,sha256=_Uefqpopw6M5NZq7WRi5sXyPMSyaCtBA7oSXObhlvM8,54250
93
+ seleniumbase/plugins/sb_manager.py,sha256=lVCPDL0Y1ig2TAOZUlJmMZs2ij-SqhYhaEd_SYZyRfQ,54256
94
94
  seleniumbase/plugins/screen_shots.py,sha256=1hrXw-hzuZ1BR6Yh7AyWX2ABnvnP73-RCbwdz958gj4,1127
95
95
  seleniumbase/plugins/selenium_plugin.py,sha256=GhGW2ATy2kM7UH7NrZ2je402nN2LMlVHpM-yxlU3I9E,59069
96
96
  seleniumbase/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -106,7 +106,7 @@ seleniumbase/translate/portuguese.py,sha256=x3P4qxp56UiI41GoaL7JbUvFRYsgXU1EKjTg
106
106
  seleniumbase/translate/russian.py,sha256=TyN9n0b4GRWDEYnHRGw1rfNAscdDmP3F3Y3aySM3C7s,27978
107
107
  seleniumbase/translate/spanish.py,sha256=hh3xgW1Pq122SHYVvJAxFaXhFrjniOVncVbJbfWqOUM,25528
108
108
  seleniumbase/translate/translator.py,sha256=wPhZH6e5NhmebYL1kP2eGxUcVy1gfTb6XCH8ATEPpxE,49238
109
- seleniumbase/undetected/__init__.py,sha256=yBUsi4T79yUVxg9yKuDdjeCP85tMiaVBpjarTniBDTQ,23002
109
+ seleniumbase/undetected/__init__.py,sha256=PSaAVgWU-yU1zwts2-UIJMWS6Mwo1YwAw0pMlSx6jVM,22998
110
110
  seleniumbase/undetected/cdp.py,sha256=RLpwZnhUvmK9tgXcZIBBQedwk2q7Jj_EXZkmzI8WUqk,4023
111
111
  seleniumbase/undetected/dprocess.py,sha256=83EV8ZHJWHG1TSUv9JNClBhdgiBXlkCc6mJ--HsoP3k,1681
112
112
  seleniumbase/undetected/options.py,sha256=BoNuwhrG7oOvuLvTwkvsWCF36pMkS1tHCG-XpP4_EkI,3001
@@ -116,7 +116,7 @@ seleniumbase/undetected/webelement.py,sha256=_s6evgUkdWJpwOnzX4qR9i796PoVbz3txlz
116
116
  seleniumbase/undetected/cdp_driver/__init__.py,sha256=c0TjMwPfVFyoqYOJ7PQ-Jln_L_dpN3ebHyaD-juQoM0,64
117
117
  seleniumbase/undetected/cdp_driver/_contradict.py,sha256=6thDYeoEGiC7Q3tXLgoD_AhxecCFnATzBSjNympyRHA,3184
118
118
  seleniumbase/undetected/cdp_driver/browser.py,sha256=mGmpWuR206yYJoBPTNslru8CbEpQuvvIHdjBylSkoKg,30020
119
- seleniumbase/undetected/cdp_driver/cdp_util.py,sha256=Erfb62fzpvGr0QIIJOiqvGkyoyBakHOIwgbrQ7dqUgM,16625
119
+ seleniumbase/undetected/cdp_driver/cdp_util.py,sha256=YhtD2Tm6PLIy9VKbgk8lHdGniS3mObyX4yAC1aG0TgQ,16733
120
120
  seleniumbase/undetected/cdp_driver/config.py,sha256=Rjvde7V-XJ0ihZdTmOmHEVWSuDWm3SprQ3njg8SN3Go,12087
121
121
  seleniumbase/undetected/cdp_driver/connection.py,sha256=sOTUGjbUqKA2hPvDcRCdqw1VQjVGJs7mbgVvzS7ldtE,23360
122
122
  seleniumbase/undetected/cdp_driver/element.py,sha256=wEHtuF9oiny7cb4QMMjdtD5Yf1z3WOs2_NHEWcscusM,40289
@@ -135,9 +135,9 @@ seleniumbase/utilities/selenium_grid/start-grid-hub.bat,sha256=Ftq-GrAKRYH2ssDPr
135
135
  seleniumbase/utilities/selenium_grid/start-grid-hub.sh,sha256=KADv0RUHONLL2_I443QFK8PryBpDmKn5Gy0s4o0vDSM,106
136
136
  seleniumbase/utilities/selenium_ide/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
137
  seleniumbase/utilities/selenium_ide/convert_ide.py,sha256=pZFnqEJQEKZPyNFjkLD29s2HPQgCrWW9XJWpCPhWOoM,31691
138
- seleniumbase-4.32.10a3.dist-info/LICENSE,sha256=odSYtWibXBnQ1gBg6CnDZ82n8kLF_if5-2nbqnEyD8k,1085
139
- seleniumbase-4.32.10a3.dist-info/METADATA,sha256=cV0euSriQjApI1C02faR-StIhkJ_d4wgmZGaTcuTDl0,86464
140
- seleniumbase-4.32.10a3.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
141
- seleniumbase-4.32.10a3.dist-info/entry_points.txt,sha256=CNrh2EKNaHYEhO6pP1RJyVLB99LkDDYX7TnUK8xfjqk,623
142
- seleniumbase-4.32.10a3.dist-info/top_level.txt,sha256=4N97aBOQ8ETCnDnokBsWb07lJfTaq3C1ZzYRxvLMxqU,19
143
- seleniumbase-4.32.10a3.dist-info/RECORD,,
138
+ seleniumbase-4.32.12.dist-info/LICENSE,sha256=odSYtWibXBnQ1gBg6CnDZ82n8kLF_if5-2nbqnEyD8k,1085
139
+ seleniumbase-4.32.12.dist-info/METADATA,sha256=P11mc7CmOJAkPcUrEMuLMWKMI5JJObPPtOTUQONNBns,86464
140
+ seleniumbase-4.32.12.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
141
+ seleniumbase-4.32.12.dist-info/entry_points.txt,sha256=CNrh2EKNaHYEhO6pP1RJyVLB99LkDDYX7TnUK8xfjqk,623
142
+ seleniumbase-4.32.12.dist-info/top_level.txt,sha256=4N97aBOQ8ETCnDnokBsWb07lJfTaq3C1ZzYRxvLMxqU,19
143
+ seleniumbase-4.32.12.dist-info/RECORD,,