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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. sbase/steps.py +9 -0
  2. seleniumbase/__version__.py +1 -1
  3. seleniumbase/behave/behave_helper.py +2 -0
  4. seleniumbase/behave/behave_sb.py +21 -8
  5. seleniumbase/common/decorators.py +3 -1
  6. seleniumbase/console_scripts/run.py +1 -0
  7. seleniumbase/console_scripts/sb_caseplans.py +3 -4
  8. seleniumbase/console_scripts/sb_install.py +142 -11
  9. seleniumbase/console_scripts/sb_mkchart.py +1 -2
  10. seleniumbase/console_scripts/sb_mkdir.py +99 -29
  11. seleniumbase/console_scripts/sb_mkfile.py +1 -2
  12. seleniumbase/console_scripts/sb_mkpres.py +1 -2
  13. seleniumbase/console_scripts/sb_mkrec.py +26 -2
  14. seleniumbase/console_scripts/sb_objectify.py +4 -5
  15. seleniumbase/console_scripts/sb_print.py +1 -1
  16. seleniumbase/console_scripts/sb_recorder.py +40 -3
  17. seleniumbase/core/browser_launcher.py +474 -151
  18. seleniumbase/core/detect_b_ver.py +258 -16
  19. seleniumbase/core/log_helper.py +15 -21
  20. seleniumbase/core/mysql.py +1 -1
  21. seleniumbase/core/recorder_helper.py +3 -0
  22. seleniumbase/core/report_helper.py +9 -12
  23. seleniumbase/core/sb_cdp.py +734 -215
  24. seleniumbase/core/sb_driver.py +46 -5
  25. seleniumbase/core/session_helper.py +2 -4
  26. seleniumbase/core/tour_helper.py +1 -2
  27. seleniumbase/drivers/atlas_drivers/__init__.py +0 -0
  28. seleniumbase/drivers/brave_drivers/__init__.py +0 -0
  29. seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
  30. seleniumbase/drivers/comet_drivers/__init__.py +0 -0
  31. seleniumbase/drivers/opera_drivers/__init__.py +0 -0
  32. seleniumbase/fixtures/base_case.py +448 -251
  33. seleniumbase/fixtures/constants.py +36 -9
  34. seleniumbase/fixtures/js_utils.py +77 -18
  35. seleniumbase/fixtures/page_actions.py +41 -13
  36. seleniumbase/fixtures/page_utils.py +19 -12
  37. seleniumbase/fixtures/shared_utils.py +64 -6
  38. seleniumbase/masterqa/master_qa.py +16 -2
  39. seleniumbase/plugins/base_plugin.py +8 -0
  40. seleniumbase/plugins/basic_test_info.py +2 -3
  41. seleniumbase/plugins/driver_manager.py +131 -5
  42. seleniumbase/plugins/page_source.py +2 -3
  43. seleniumbase/plugins/pytest_plugin.py +244 -79
  44. seleniumbase/plugins/sb_manager.py +143 -20
  45. seleniumbase/plugins/selenium_plugin.py +144 -12
  46. seleniumbase/translate/translator.py +2 -3
  47. seleniumbase/undetected/__init__.py +17 -13
  48. seleniumbase/undetected/cdp.py +1 -12
  49. seleniumbase/undetected/cdp_driver/browser.py +330 -129
  50. seleniumbase/undetected/cdp_driver/cdp_util.py +328 -61
  51. seleniumbase/undetected/cdp_driver/config.py +110 -14
  52. seleniumbase/undetected/cdp_driver/connection.py +18 -48
  53. seleniumbase/undetected/cdp_driver/element.py +105 -33
  54. seleniumbase/undetected/cdp_driver/tab.py +414 -39
  55. seleniumbase/utilities/selenium_grid/download_selenium_server.py +1 -1
  56. seleniumbase/utilities/selenium_grid/grid_hub.py +1 -2
  57. seleniumbase/utilities/selenium_grid/grid_node.py +2 -3
  58. seleniumbase/utilities/selenium_ide/convert_ide.py +2 -3
  59. {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/METADATA +193 -166
  60. {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/RECORD +64 -59
  61. {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/licenses/LICENSE +1 -1
  62. {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/WHEEL +0 -0
  63. {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/entry_points.txt +0 -0
  64. {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/top_level.txt +0 -0
@@ -32,7 +32,6 @@ Improvements include making WebDriver more robust, reliable, and flexible.
32
32
  Page elements are given enough time to load before WebDriver acts on them.
33
33
  Code becomes greatly simplified and easier to maintain."""
34
34
 
35
- import codecs
36
35
  import colorama
37
36
  import fasteners
38
37
  import json
@@ -114,9 +113,7 @@ class BaseCase(unittest.TestCase):
114
113
  self.driver = None
115
114
  self.environment = None
116
115
  self.env = None # Add a shortened version of self.environment
117
- self.version_list = [
118
- int(i) for i in __version__.split(".") if i.isdigit()
119
- ]
116
+ self.version_list = shared_utils.make_version_list(__version__)
120
117
  self.version_tuple = tuple(self.version_list)
121
118
  self.version_info = self.version_tuple
122
119
  self.time = time.time
@@ -134,6 +131,7 @@ class BaseCase(unittest.TestCase):
134
131
  self.__requests_timeout = None
135
132
  self.__page_source_count = 0
136
133
  self.__screenshot_count = 0
134
+ self.__saved_pdf_count = 0
137
135
  self.__logs_data_count = 0
138
136
  self.__last_data_file = None
139
137
  self.__level_0_visual_f = False
@@ -223,11 +221,28 @@ class BaseCase(unittest.TestCase):
223
221
  [sys.executable, "-m", "pytest", file, "-s", *all_args]
224
222
  )
225
223
 
226
- def open(self, url):
224
+ def open(self, url, **kwargs):
227
225
  """Navigates the current browser window to the specified page."""
228
226
  self.__check_scope()
229
227
  if self.__is_cdp_swap_needed():
230
- self.cdp.open(url)
228
+ self.cdp.open(url, **kwargs)
229
+ return
230
+ elif (
231
+ getattr(self.driver, "_is_using_uc", None)
232
+ # and getattr(self.driver, "_is_using_auth", None)
233
+ and not getattr(self.driver, "_is_using_cdp", None)
234
+ ):
235
+ # Auth in UC Mode requires CDP Mode
236
+ # (and now we're always forcing it)
237
+ logging.info("open() in UC Mode now always activates CDP Mode.")
238
+ self.activate_cdp_mode(url, **kwargs)
239
+ return
240
+ elif (
241
+ getattr(self.driver, "_is_using_uc", None)
242
+ and getattr(self.driver, "_is_using_cdp", None)
243
+ ):
244
+ self.disconnect()
245
+ self.cdp.open(url, **kwargs)
231
246
  return
232
247
  self._check_browser()
233
248
  if self.__needs_minimum_wait():
@@ -1295,8 +1310,11 @@ class BaseCase(unittest.TestCase):
1295
1310
  self.__check_scope()
1296
1311
  return self.execute_script("return window.location.origin;")
1297
1312
 
1298
- def get_page_source(self):
1299
- if self.__is_cdp_swap_needed():
1313
+ def get_html(self, *args, **kwargs):
1314
+ return self.get_page_source(*args, **kwargs)
1315
+
1316
+ def get_page_source(self, *args, **kwargs):
1317
+ if self.__is_cdp_swap_needed(*args, **kwargs):
1300
1318
  return self.cdp.get_page_source()
1301
1319
  self.wait_for_ready_state_complete()
1302
1320
  if self.__needs_minimum_wait:
@@ -1335,7 +1353,7 @@ class BaseCase(unittest.TestCase):
1335
1353
  if self.__is_cdp_swap_needed():
1336
1354
  self.cdp.go_back()
1337
1355
  return
1338
- if hasattr(self, "recorder_mode") and self.recorder_mode:
1356
+ if getattr(self, "recorder_mode", None):
1339
1357
  self.save_recorded_actions()
1340
1358
  pre_action_url = None
1341
1359
  with suppress(Exception):
@@ -1363,7 +1381,7 @@ class BaseCase(unittest.TestCase):
1363
1381
  if self.__is_cdp_swap_needed():
1364
1382
  self.cdp.go_forward()
1365
1383
  return
1366
- if hasattr(self, "recorder_mode") and self.recorder_mode:
1384
+ if getattr(self, "recorder_mode", None):
1367
1385
  self.save_recorded_actions()
1368
1386
  self.__last_page_load_url = None
1369
1387
  self.driver.forward()
@@ -1502,6 +1520,10 @@ class BaseCase(unittest.TestCase):
1502
1520
  ):
1503
1521
  """Returns True if the element attribute/value is found.
1504
1522
  If the value is not specified, the attribute only needs to exist."""
1523
+ if self.__is_cdp_swap_needed():
1524
+ return self.cdp.is_attribute_present(
1525
+ selector, attribute, value=value
1526
+ )
1505
1527
  self.wait_for_ready_state_complete()
1506
1528
  time.sleep(0.01)
1507
1529
  selector, by = self.__recalculate_selector(selector, by)
@@ -1615,14 +1637,14 @@ class BaseCase(unittest.TestCase):
1615
1637
  def click_link_text(self, link_text, timeout=None):
1616
1638
  """This method clicks link text on a page."""
1617
1639
  self.__check_scope()
1618
- if self.__is_cdp_swap_needed():
1619
- self.cdp.find_element(link_text, timeout=timeout).click()
1620
- return
1621
- self.__skip_if_esc()
1622
1640
  if not timeout:
1623
1641
  timeout = settings.SMALL_TIMEOUT
1624
1642
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
1625
1643
  timeout = self.__get_new_timeout(timeout)
1644
+ if self.__is_cdp_swap_needed():
1645
+ self.cdp.find_element(link_text, timeout=timeout).click()
1646
+ return
1647
+ self.__skip_if_esc()
1626
1648
  link_text = self.__get_type_checked_text(link_text)
1627
1649
  if self.__is_cdp_swap_needed():
1628
1650
  self.cdp.click_link(link_text)
@@ -2695,7 +2717,7 @@ class BaseCase(unittest.TestCase):
2695
2717
  original_by = by
2696
2718
  selector, by = self.__recalculate_selector(selector, by)
2697
2719
  if self.__is_cdp_swap_needed():
2698
- self.cdp.gui_hover_element(selector)
2720
+ self.cdp.hover_element(selector)
2699
2721
  return
2700
2722
  self.wait_for_element_visible(
2701
2723
  original_selector, by=original_by, timeout=timeout
@@ -2740,7 +2762,7 @@ class BaseCase(unittest.TestCase):
2740
2762
  click_selector, click_by
2741
2763
  )
2742
2764
  if self.__is_cdp_swap_needed():
2743
- self.cdp.gui_hover_and_click(hover_selector, click_selector)
2765
+ self.cdp.hover_and_click(hover_selector, click_selector)
2744
2766
  return
2745
2767
  dropdown_element = self.wait_for_element_visible(
2746
2768
  original_selector, by=original_by, timeout=timeout
@@ -3198,6 +3220,9 @@ class BaseCase(unittest.TestCase):
3198
3220
  timeout = settings.SMALL_TIMEOUT
3199
3221
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
3200
3222
  timeout = self.__get_new_timeout(timeout)
3223
+ if self.__is_cdp_swap_needed():
3224
+ self.cdp.select_option_by_index(dropdown_selector, option)
3225
+ return
3201
3226
  self.__select_option(
3202
3227
  dropdown_selector,
3203
3228
  option,
@@ -3222,6 +3247,9 @@ class BaseCase(unittest.TestCase):
3222
3247
  timeout = settings.SMALL_TIMEOUT
3223
3248
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
3224
3249
  timeout = self.__get_new_timeout(timeout)
3250
+ if self.__is_cdp_swap_needed():
3251
+ self.cdp.select_option_by_value(dropdown_selector, option)
3252
+ return
3225
3253
  self.__select_option(
3226
3254
  dropdown_selector,
3227
3255
  option,
@@ -3418,6 +3446,46 @@ class BaseCase(unittest.TestCase):
3418
3446
  file_path = os.path.join(abs_path, html_file)
3419
3447
  self.open("file://" + file_path)
3420
3448
 
3449
+ def evaluate(self, expression):
3450
+ """Run a JavaScript expression and return the result."""
3451
+ self.__check_scope()
3452
+ if self.__is_cdp_swap_needed():
3453
+ return self.cdp.evaluate(expression)
3454
+ self._check_browser()
3455
+ original_expression = expression
3456
+ expression = expression.strip()
3457
+ exp_list = expression.split("\n")
3458
+ if exp_list and exp_list[-1].strip().startswith("return "):
3459
+ expression = (
3460
+ "\n".join(exp_list[0:-1]) + "\n"
3461
+ + exp_list[-1].strip()[len("return "):]
3462
+ ).strip()
3463
+ evaluation = self.driver.execute_cdp_cmd(
3464
+ "Runtime.evaluate",
3465
+ {
3466
+ "expression": expression
3467
+ },
3468
+ )
3469
+ if "value" in evaluation["result"]:
3470
+ return evaluation["result"]["value"]
3471
+ elif evaluation["result"]["type"] == "undefined":
3472
+ return None
3473
+ elif "exceptionDetails" in evaluation:
3474
+ raise Exception(evaluation["result"]["description"], expression)
3475
+ elif evaluation["result"]["type"] == "object":
3476
+ if "return " not in original_expression:
3477
+ expression = "return " + original_expression.strip()
3478
+ # Need to use execute_script() to return a WebDriver object.
3479
+ # If this causes duplicate evaluation, don't use evaluate().
3480
+ return self.execute_script(expression)
3481
+ elif evaluation["result"]["type"] == "function":
3482
+ return {} # This is what sb.cdp.evaluate returns
3483
+ elif "description" in evaluation["result"]:
3484
+ # At this point, the description is the exception
3485
+ raise Exception(evaluation["result"]["description"], expression)
3486
+ else: # Possibly an unhandled case if reached
3487
+ return None
3488
+
3421
3489
  def execute_script(self, script, *args, **kwargs):
3422
3490
  self.__check_scope()
3423
3491
  if self.__is_cdp_swap_needed():
@@ -3569,9 +3637,18 @@ class BaseCase(unittest.TestCase):
3569
3637
  self.cdp.maximize()
3570
3638
  return
3571
3639
  self._check_browser()
3572
- self.driver.maximize_window()
3640
+ try:
3641
+ self.driver.maximize_window()
3642
+ except Exception:
3643
+ with suppress(Exception):
3644
+ width = self.execute_script("return screen.availWidth;")
3645
+ height = self.execute_script("return screen.availHeight;")
3646
+ self.set_window_rect(0, 0, width, height)
3573
3647
  self.__demo_mode_pause_if_active(tiny=True)
3574
3648
 
3649
+ def maximize(self):
3650
+ self.maximize_window()
3651
+
3575
3652
  def minimize_window(self):
3576
3653
  self.__check_scope()
3577
3654
  if self.__is_cdp_swap_needed():
@@ -3581,6 +3658,9 @@ class BaseCase(unittest.TestCase):
3581
3658
  self.driver.minimize_window()
3582
3659
  self.__demo_mode_pause_if_active(tiny=True)
3583
3660
 
3661
+ def minimize(self):
3662
+ self.minimize_window()
3663
+
3584
3664
  def reset_window_size(self):
3585
3665
  self.__check_scope()
3586
3666
  if self.__is_cdp_swap_needed():
@@ -3920,10 +4000,23 @@ class BaseCase(unittest.TestCase):
3920
4000
  Reverts self.set_content_to_frame()."""
3921
4001
  self.set_content_to_default(nested=True)
3922
4002
 
3923
- def open_new_window(self, switch_to=True):
4003
+ def open_new_window(self, switch_to=True, **kwargs):
3924
4004
  """Opens a new browser tab/window and switches to it by default."""
4005
+ url = None
4006
+ if self.__looks_like_a_page_url(str(switch_to)):
4007
+ # Different API for CDP Mode: First arg is a `url`.
4008
+ # (Also, don't break backwards compat for reg mode)
4009
+ url = switch_to
4010
+ switch_to = True
3925
4011
  if self.__is_cdp_swap_needed():
3926
- self.cdp.open_new_tab(switch_to=switch_to)
4012
+ self.cdp.open_new_tab(url=url, switch_to=switch_to, **kwargs)
4013
+ return
4014
+ elif (
4015
+ getattr(self.driver, "_is_using_uc", None)
4016
+ and getattr(self.driver, "_is_using_cdp", None)
4017
+ ):
4018
+ self.disconnect()
4019
+ self.cdp.open_new_tab(url=url, switch_to=switch_to, **kwargs)
3927
4020
  return
3928
4021
  self.wait_for_ready_state_complete()
3929
4022
  if switch_to:
@@ -4336,7 +4429,7 @@ class BaseCase(unittest.TestCase):
4336
4429
  if self.is_chromium():
4337
4430
  try:
4338
4431
  if self.maximize_option:
4339
- self.driver.maximize_window()
4432
+ self.maximize_window()
4340
4433
  self.wait_for_ready_state_complete()
4341
4434
  else:
4342
4435
  pass # Now handled in browser_launcher.py
@@ -4346,7 +4439,7 @@ class BaseCase(unittest.TestCase):
4346
4439
  elif self.browser == "firefox":
4347
4440
  try:
4348
4441
  if self.maximize_option:
4349
- self.driver.maximize_window()
4442
+ self.maximize_window()
4350
4443
  self.wait_for_ready_state_complete()
4351
4444
  else:
4352
4445
  with suppress(Exception):
@@ -4356,7 +4449,7 @@ class BaseCase(unittest.TestCase):
4356
4449
  elif self.browser == "safari":
4357
4450
  if self.maximize_option:
4358
4451
  try:
4359
- self.driver.maximize_window()
4452
+ self.maximize_window()
4360
4453
  self.wait_for_ready_state_complete()
4361
4454
  except Exception:
4362
4455
  pass # Keep existing browser resolution
@@ -4486,7 +4579,8 @@ class BaseCase(unittest.TestCase):
4486
4579
  If a provided selector is not found, then takes a full-page screenshot.
4487
4580
  (The last_page / failure screenshot is always "screenshot.png")
4488
4581
  The screenshot will be in PNG format."""
4489
- self.wait_for_ready_state_complete()
4582
+ if not self.__is_cdp_swap_needed():
4583
+ self.wait_for_ready_state_complete()
4490
4584
  test_logpath = os.path.join(self.log_path, self.__get_test_id())
4491
4585
  self.__create_log_path_as_needed(test_logpath)
4492
4586
  if name:
@@ -4504,6 +4598,11 @@ class BaseCase(unittest.TestCase):
4504
4598
  if selector and by:
4505
4599
  selector, by = self.__recalculate_selector(selector, by)
4506
4600
  if page_actions.is_element_present(self.driver, selector, by):
4601
+ if self.__is_cdp_swap_needed():
4602
+ selector = self.convert_to_css_selector(selector, by=by)
4603
+ return self.cdp.save_screenshot(
4604
+ name, folder=test_logpath, selector=selector
4605
+ )
4507
4606
  return page_actions.save_screenshot(
4508
4607
  self.driver, name, test_logpath, selector, by
4509
4608
  )
@@ -4517,8 +4616,49 @@ class BaseCase(unittest.TestCase):
4517
4616
  action = ["ss_tl", "", origin, time_stamp]
4518
4617
  self.__extra_actions.append(action)
4519
4618
  sb_config._has_logs = True
4619
+ if self.__is_cdp_swap_needed():
4620
+ return self.cdp.save_screenshot(name, folder=test_logpath)
4520
4621
  return page_actions.save_screenshot(self.driver, name, test_logpath)
4521
4622
 
4623
+ def save_as_pdf(self, name, folder=None):
4624
+ """Same as self.print_to_pdf()"""
4625
+ return self.print_to_pdf(name, folder=folder)
4626
+
4627
+ def save_as_pdf_to_logs(self, name=None):
4628
+ """Saves the page as a PDF to the "latest_logs/" folder.
4629
+ Naming is automatic:
4630
+ If NO NAME provided: "_1_PDF.pdf", "_2_PDF.pdf", etc.
4631
+ If NAME IS provided, then: "_1_name.pdf", "_2_name.pdf", etc."""
4632
+ if not self.__is_cdp_swap_needed():
4633
+ self.wait_for_ready_state_complete()
4634
+ test_logpath = os.path.join(self.log_path, self.__get_test_id())
4635
+ self.__create_log_path_as_needed(test_logpath)
4636
+ if name:
4637
+ name = str(name)
4638
+ self.__saved_pdf_count += 1
4639
+ if not name or len(name) == 0:
4640
+ name = "_%s_PDF.pdf" % self.__saved_pdf_count
4641
+ else:
4642
+ pre_name = "_%s_" % self.__saved_pdf_count
4643
+ if len(name) >= 4 and name[-4:].lower() == ".pdf":
4644
+ name = name[:-4]
4645
+ if len(name) == 0:
4646
+ name = "PDF"
4647
+ name = "%s%s.pdf" % (pre_name, name)
4648
+ if self.recorder_mode:
4649
+ url = self.get_current_url()
4650
+ if url and len(url) > 0:
4651
+ if ("http:") in url or ("https:") in url or ("file:") in url:
4652
+ if self.get_session_storage_item("pause_recorder") == "no":
4653
+ time_stamp = self.execute_script("return Date.now();")
4654
+ origin = self.get_origin()
4655
+ action = ["pdftl", "", origin, time_stamp]
4656
+ self.__extra_actions.append(action)
4657
+ sb_config._has_logs = True
4658
+ if self.__is_cdp_swap_needed():
4659
+ return self.cdp.print_to_pdf(name, folder=test_logpath)
4660
+ return self.print_to_pdf(name, test_logpath)
4661
+
4522
4662
  def save_page_source_to_logs(self, name=None):
4523
4663
  """Saves the page HTML to the "latest_logs/" folder.
4524
4664
  Naming is automatic:
@@ -4627,7 +4767,7 @@ class BaseCase(unittest.TestCase):
4627
4767
  if not os.path.exists(file_path):
4628
4768
  os.makedirs(file_path)
4629
4769
  cookies_file_path = os.path.join(file_path, name)
4630
- cookies_file = codecs.open(cookies_file_path, "w+", encoding="utf-8")
4770
+ cookies_file = open(cookies_file_path, mode="w+", encoding="utf-8")
4631
4771
  cookies_file.writelines(json_cookies)
4632
4772
  cookies_file.close()
4633
4773
 
@@ -4786,7 +4926,7 @@ class BaseCase(unittest.TestCase):
4786
4926
  self.driver.add_cookie(cookie)
4787
4927
 
4788
4928
  def __set_esc_skip(self):
4789
- if hasattr(self, "esc_end") and self.esc_end:
4929
+ if getattr(self, "esc_end", None):
4790
4930
  script = (
4791
4931
  """document.onkeydown = function(evt) {
4792
4932
  evt = evt || window.event;
@@ -4804,7 +4944,7 @@ class BaseCase(unittest.TestCase):
4804
4944
  self.execute_script(script)
4805
4945
 
4806
4946
  def __skip_if_esc(self):
4807
- if hasattr(self, "esc_end") and self.esc_end:
4947
+ if getattr(self, "esc_end", None):
4808
4948
  if self.execute_script("return document.sb_esc_end;") == "yes":
4809
4949
  self.skip()
4810
4950
 
@@ -4826,8 +4966,7 @@ class BaseCase(unittest.TestCase):
4826
4966
  self.__disable_beforeunload_as_needed()
4827
4967
  if (
4828
4968
  self.page_load_strategy == "none"
4829
- and hasattr(settings, "SKIP_JS_WAITS")
4830
- and settings.SKIP_JS_WAITS
4969
+ and getattr(settings, "SKIP_JS_WAITS", None)
4831
4970
  ):
4832
4971
  time.sleep(0.01)
4833
4972
  if self.undetectable:
@@ -4848,10 +4987,7 @@ class BaseCase(unittest.TestCase):
4848
4987
 
4849
4988
  def sleep(self, seconds):
4850
4989
  self.__check_scope()
4851
- if (
4852
- not hasattr(sb_config, "time_limit")
4853
- or (hasattr(sb_config, "time_limit") and not sb_config.time_limit)
4854
- ):
4990
+ if not getattr(sb_config, "time_limit", None):
4855
4991
  time.sleep(seconds)
4856
4992
  elif seconds < 0.4:
4857
4993
  shared_utils.check_if_time_limit_exceeded()
@@ -4866,11 +5002,7 @@ class BaseCase(unittest.TestCase):
4866
5002
  if now_ms >= stop_ms:
4867
5003
  break
4868
5004
  time.sleep(0.2)
4869
- if (
4870
- self.recorder_mode
4871
- and hasattr(sb_config, "record_sleep")
4872
- and sb_config.record_sleep
4873
- ):
5005
+ if self.recorder_mode and getattr(sb_config, "record_sleep", None):
4874
5006
  time_stamp = self.execute_script("return Date.now();")
4875
5007
  origin = self.get_origin()
4876
5008
  action = ["sleep", seconds, origin, time_stamp]
@@ -4928,23 +5060,32 @@ class BaseCase(unittest.TestCase):
4928
5060
 
4929
5061
  def activate_cdp_mode(self, url=None, **kwargs):
4930
5062
  """Activate CDP Mode with the URL and kwargs."""
4931
- if hasattr(self.driver, "_is_using_uc") and self.driver._is_using_uc:
5063
+ if getattr(self.driver, "_is_using_uc", None):
4932
5064
  if self.__is_cdp_swap_needed():
4933
5065
  return # CDP Mode is already active
4934
5066
  if not self.is_connected():
4935
5067
  self.driver.connect()
4936
5068
  current_url = self.get_current_url()
4937
5069
  if not current_url.startswith(("about", "data", "chrome")):
4938
- self.open("about:blank")
5070
+ self.driver.get("about:blank")
4939
5071
  self.driver.uc_open_with_cdp_mode(url, **kwargs)
4940
5072
  else:
4941
5073
  self.get_new_driver(undetectable=True)
4942
5074
  self.driver.uc_open_with_cdp_mode(url, **kwargs)
4943
5075
  self.cdp = self.driver.cdp
5076
+ if hasattr(self.cdp, "solve_captcha"):
5077
+ self.solve_captcha = self.cdp.solve_captcha
5078
+ if hasattr(self.cdp, "click_captcha"):
5079
+ self.click_captcha = self.cdp.click_captcha
5080
+ if hasattr(self.cdp, "find_element_by_text"):
5081
+ self.find_element_by_text = self.cdp.find_element_by_text
5082
+ if getattr(self.driver, "_is_using_auth", None):
5083
+ with suppress(Exception):
5084
+ self.cdp.loop.run_until_complete(self.cdp.page.wait(0.25))
5085
+ self.undetectable = True
4944
5086
 
4945
5087
  def activate_recorder(self):
4946
- """Activate Recorder Mode on the current tab/window.
4947
- For persistent Recorder Mode, use the extension instead."""
5088
+ """Activate Recorder Mode on the newest tab / window."""
4948
5089
  from seleniumbase.js_code.recorder_js import recorder_js
4949
5090
 
4950
5091
  if not self.is_chromium():
@@ -4957,7 +5098,7 @@ class BaseCase(unittest.TestCase):
4957
5098
  "The %s Recorder is for Chromium only!\n"
4958
5099
  " (Supported browsers: Chrome and Edge)" % sc
4959
5100
  )
4960
- url = self.driver.current_url
5101
+ url = self.get_current_url()
4961
5102
  if url.startswith(("data:", "about:", "chrome:", "edge:")):
4962
5103
  message = (
4963
5104
  "The URL in Recorder-Mode cannot start with: "
@@ -4965,8 +5106,9 @@ class BaseCase(unittest.TestCase):
4965
5106
  )
4966
5107
  print("\n" + message)
4967
5108
  return
4968
- if self.recorder_ext:
5109
+ if self.recorder_ext and not self.undetectable:
4969
5110
  return # The Recorder extension is already active
5111
+ self.switch_to_newest_tab()
4970
5112
  with suppress(Exception):
4971
5113
  recorder_on = self.get_session_storage_item("recorder_activated")
4972
5114
  if not recorder_on == "yes":
@@ -5365,6 +5507,20 @@ class BaseCase(unittest.TestCase):
5365
5507
  )
5366
5508
  ):
5367
5509
  srt_actions[n][0] = "_skip"
5510
+ for n in range(len(srt_actions)):
5511
+ if (
5512
+ (srt_actions[n][0] == "begin" or srt_actions[n][0] == "_url_")
5513
+ and n > 1
5514
+ and (
5515
+ srt_actions[n - 1][0] == "f_url"
5516
+ or srt_actions[n - 1][0] == "_url_"
5517
+ )
5518
+ and srt_actions[n][2] == srt_actions[n - 1][2]
5519
+ and (
5520
+ int(srt_actions[n][3]) - int(srt_actions[n - 1][3]) < 4800
5521
+ )
5522
+ ):
5523
+ srt_actions[n][0] = "_skip"
5368
5524
  for n in range(len(srt_actions)):
5369
5525
  if (
5370
5526
  srt_actions[n][0] == "input"
@@ -5511,6 +5667,7 @@ class BaseCase(unittest.TestCase):
5511
5667
  ext_actions.append("s_scr")
5512
5668
  ext_actions.append("ss_tf")
5513
5669
  ext_actions.append("ss_tl")
5670
+ ext_actions.append("pdftl")
5514
5671
  ext_actions.append("spstl")
5515
5672
  ext_actions.append("da_el")
5516
5673
  ext_actions.append("da_ep")
@@ -5574,14 +5731,13 @@ class BaseCase(unittest.TestCase):
5574
5731
  methodname = self._testMethodName
5575
5732
  context_filename = None
5576
5733
  if (
5577
- hasattr(sb_config, "is_context_manager")
5578
- and sb_config.is_context_manager
5734
+ getattr(sb_config, "is_context_manager", None)
5579
5735
  and (filename == "base_case.py" or methodname == "runTest")
5580
5736
  ):
5581
5737
  import traceback
5582
5738
  stack_base = traceback.format_stack()[0].split(os.sep)[-1]
5583
5739
  test_base = stack_base.split(", in ")[0]
5584
- if hasattr(self, "cm_filename") and self.cm_filename:
5740
+ if getattr(self, "cm_filename", None):
5585
5741
  filename = self.cm_filename
5586
5742
  else:
5587
5743
  filename = test_base.split('"')[0]
@@ -5598,7 +5754,7 @@ class BaseCase(unittest.TestCase):
5598
5754
  classname = "MyTestClass"
5599
5755
  methodname = methodname.replace("[", "__").replace("]", "")
5600
5756
  methodname = re.sub(r"[\W]", "_", methodname)
5601
- if hasattr(self, "is_behave") and self.is_behave:
5757
+ if getattr(self, "is_behave", None):
5602
5758
  classname = sb_config.behave_feature.name
5603
5759
  classname = classname.replace("/", " ").replace(" & ", " ")
5604
5760
  classname = re.sub(r"[^\w" + r"_ " + r"]", "", classname)
@@ -5666,7 +5822,7 @@ class BaseCase(unittest.TestCase):
5666
5822
  extra_file_name = "__init__.py"
5667
5823
  extra_file_path = os.path.join(recordings_folder, extra_file_name)
5668
5824
  if not os.path.exists(extra_file_path):
5669
- out_file = codecs.open(extra_file_path, "w+", "utf-8")
5825
+ out_file = open(extra_file_path, mode="w+", encoding="utf-8")
5670
5826
  out_file.writelines("\r\n".join(data))
5671
5827
  out_file.close()
5672
5828
  sys.stdout.write("\nCreated recordings%s__init__.py" % os.sep)
@@ -5714,7 +5870,7 @@ class BaseCase(unittest.TestCase):
5714
5870
  extra_file_name = "pytest.ini"
5715
5871
  extra_file_path = os.path.join(recordings_folder, extra_file_name)
5716
5872
  if not os.path.exists(extra_file_path):
5717
- out_file = codecs.open(extra_file_path, "w+", "utf-8")
5873
+ out_file = open(extra_file_path, mode="w+", encoding="utf-8")
5718
5874
  out_file.writelines("\r\n".join(data))
5719
5875
  out_file.close()
5720
5876
  sys.stdout.write("\nCreated recordings%spytest.ini" % os.sep)
@@ -5735,7 +5891,7 @@ class BaseCase(unittest.TestCase):
5735
5891
  extra_file_name = "setup.cfg"
5736
5892
  extra_file_path = os.path.join(recordings_folder, extra_file_name)
5737
5893
  if not os.path.exists(extra_file_path):
5738
- out_file = codecs.open(extra_file_path, "w+", "utf-8")
5894
+ out_file = open(extra_file_path, mode="w+", encoding="utf-8")
5739
5895
  out_file.writelines("\r\n".join(data))
5740
5896
  out_file.close()
5741
5897
  sys.stdout.write("\nCreated recordings%ssetup.cfg" % os.sep)
@@ -5746,14 +5902,14 @@ class BaseCase(unittest.TestCase):
5746
5902
  test_id = sb_config._test_id
5747
5903
  file_name = test_id.split("::")[0].split("/")[-1].split("\\")[-1]
5748
5904
  file_name = file_name.split(".py")[0] + "_rec.py"
5749
- if hasattr(self, "is_behave") and self.is_behave:
5905
+ if getattr(self, "is_behave", None):
5750
5906
  file_name = sb_config.behave_scenario.filename.replace(".", "_")
5751
5907
  file_name = file_name.split("/")[-1].split("\\")[-1] + "_rec.py"
5752
5908
  file_name = file_name
5753
5909
  elif context_filename:
5754
5910
  file_name = context_filename
5755
5911
  file_path = os.path.join(recordings_folder, file_name)
5756
- out_file = codecs.open(file_path, "w+", "utf-8")
5912
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5757
5913
  out_file.writelines("\r\n".join(data))
5758
5914
  out_file.close()
5759
5915
  rec_message = ">>> RECORDING SAVED as: "
@@ -5765,7 +5921,7 @@ class BaseCase(unittest.TestCase):
5765
5921
  if terminal_size > 30 and star_len > terminal_size:
5766
5922
  star_len = terminal_size
5767
5923
  spc = "\n\n"
5768
- if hasattr(self, "rec_print") and self.rec_print:
5924
+ if getattr(self, "rec_print", None):
5769
5925
  spc = ""
5770
5926
  sys.stdout.write("\nCreated recordings%s%s" % (os.sep, file_name))
5771
5927
  print()
@@ -5786,7 +5942,7 @@ class BaseCase(unittest.TestCase):
5786
5942
  rec_message = rec_message.replace(">>>", c2 + ">>>" + cr)
5787
5943
  print("%s%s%s%s%s\n%s" % (spc, rec_message, c1, file_path, cr, stars))
5788
5944
 
5789
- if hasattr(self, "rec_behave") and self.rec_behave:
5945
+ if getattr(self, "rec_behave", None):
5790
5946
  # Also generate necessary behave-gherkin files.
5791
5947
  self.__process_recorded_behave_actions(srt_actions, colorama)
5792
5948
 
@@ -5797,7 +5953,7 @@ class BaseCase(unittest.TestCase):
5797
5953
  filename = self.__get_filename()
5798
5954
  feature_class = None
5799
5955
  scenario_test = None
5800
- if hasattr(self, "is_behave") and self.is_behave:
5956
+ if getattr(self, "is_behave", None):
5801
5957
  feature_class = sb_config.behave_feature.name
5802
5958
  scenario_test = sb_config.behave_scenario.name
5803
5959
  else:
@@ -5851,11 +6007,11 @@ class BaseCase(unittest.TestCase):
5851
6007
  os.makedirs(steps_folder)
5852
6008
 
5853
6009
  file_name = filename.split(".")[0]
5854
- if hasattr(self, "is_behave") and self.is_behave:
6010
+ if getattr(self, "is_behave", None):
5855
6011
  file_name = sb_config.behave_scenario.filename.replace(".", "_")
5856
6012
  file_name = file_name.split("/")[-1].split("\\")[-1] + "_rec.feature"
5857
6013
  file_path = os.path.join(features_folder, file_name)
5858
- out_file = codecs.open(file_path, "w+", "utf-8")
6014
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5859
6015
  out_file.writelines("\r\n".join(data))
5860
6016
  out_file.close()
5861
6017
 
@@ -5868,7 +6024,7 @@ class BaseCase(unittest.TestCase):
5868
6024
  if terminal_size > 30 and star_len > terminal_size:
5869
6025
  star_len = terminal_size
5870
6026
  spc = "\n"
5871
- if hasattr(self, "rec_print") and self.rec_print:
6027
+ if getattr(self, "rec_print", None):
5872
6028
  spc = ""
5873
6029
  print()
5874
6030
  if " " not in file_path:
@@ -5893,7 +6049,7 @@ class BaseCase(unittest.TestCase):
5893
6049
  file_name = "__init__.py"
5894
6050
  file_path = os.path.join(features_folder, file_name)
5895
6051
  if not os.path.exists(file_path):
5896
- out_file = codecs.open(file_path, "w+", "utf-8")
6052
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5897
6053
  out_file.writelines("\r\n".join(data))
5898
6054
  out_file.close()
5899
6055
  print("Created recordings/features/__init__.py")
@@ -5906,7 +6062,7 @@ class BaseCase(unittest.TestCase):
5906
6062
  file_name = "behave.ini"
5907
6063
  file_path = os.path.join(features_folder, file_name)
5908
6064
  if not os.path.exists(file_path):
5909
- out_file = codecs.open(file_path, "w+", "utf-8")
6065
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5910
6066
  out_file.writelines("\r\n".join(data))
5911
6067
  out_file.close()
5912
6068
  print("Created recordings/features/behave.ini")
@@ -5945,7 +6101,7 @@ class BaseCase(unittest.TestCase):
5945
6101
  file_name = "environment.py"
5946
6102
  file_path = os.path.join(features_folder, file_name)
5947
6103
  if not os.path.exists(file_path):
5948
- out_file = codecs.open(file_path, "w+", "utf-8")
6104
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5949
6105
  out_file.writelines("\r\n".join(data))
5950
6106
  out_file.close()
5951
6107
  print("Created recordings/features/environment.py")
@@ -5955,7 +6111,7 @@ class BaseCase(unittest.TestCase):
5955
6111
  file_name = "__init__.py"
5956
6112
  file_path = os.path.join(steps_folder, file_name)
5957
6113
  if not os.path.exists(file_path):
5958
- out_file = codecs.open(file_path, "w+", "utf-8")
6114
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5959
6115
  out_file.writelines("\r\n".join(data))
5960
6116
  out_file.close()
5961
6117
  print("Created recordings/features/steps/__init__.py")
@@ -5966,7 +6122,7 @@ class BaseCase(unittest.TestCase):
5966
6122
  file_name = "imported.py"
5967
6123
  file_path = os.path.join(steps_folder, file_name)
5968
6124
  if not os.path.exists(file_path):
5969
- out_file = codecs.open(file_path, "w+", "utf-8")
6125
+ out_file = open(file_path, mode="w+", encoding="utf-8")
5970
6126
  out_file.writelines("\r\n".join(data))
5971
6127
  out_file.close()
5972
6128
  print("Created recordings/features/steps/imported.py")
@@ -6193,7 +6349,12 @@ class BaseCase(unittest.TestCase):
6193
6349
  scroll - the option to scroll to the element first (Default: True)
6194
6350
  timeout - the time to wait for the element to appear """
6195
6351
  self.__check_scope()
6196
- if not self.__is_cdp_swap_needed():
6352
+ if self.__is_cdp_swap_needed():
6353
+ if page_utils.is_xpath_selector(selector):
6354
+ if "contains(" in selector:
6355
+ self.cdp.highlight(selector)
6356
+ return
6357
+ else:
6197
6358
  self._check_browser()
6198
6359
  self.__skip_if_esc()
6199
6360
  if isinstance(selector, WebElement):
@@ -6238,6 +6399,7 @@ class BaseCase(unittest.TestCase):
6238
6399
  By default, "html" will be used as the CSS Selector target.
6239
6400
  You can specify how many times in-a-row the action happens."""
6240
6401
  self.__check_scope()
6402
+ self._check_browser()
6241
6403
  if times < 1:
6242
6404
  return
6243
6405
  element = self.wait_for_element_present(selector)
@@ -6260,6 +6422,7 @@ class BaseCase(unittest.TestCase):
6260
6422
  By default, "html" will be used as the CSS Selector target.
6261
6423
  You can specify how many times in-a-row the action happens."""
6262
6424
  self.__check_scope()
6425
+ self._check_browser()
6263
6426
  if times < 1:
6264
6427
  return
6265
6428
  element = self.wait_for_element_present(selector)
@@ -6282,6 +6445,7 @@ class BaseCase(unittest.TestCase):
6282
6445
  By default, "html" will be used as the CSS Selector target.
6283
6446
  You can specify how many times in-a-row the action happens."""
6284
6447
  self.__check_scope()
6448
+ self._check_browser()
6285
6449
  if times < 1:
6286
6450
  return
6287
6451
  element = self.wait_for_element_present(selector)
@@ -6304,6 +6468,7 @@ class BaseCase(unittest.TestCase):
6304
6468
  By default, "html" will be used as the CSS Selector target.
6305
6469
  You can specify how many times in-a-row the action happens."""
6306
6470
  self.__check_scope()
6471
+ self._check_browser()
6307
6472
  if times < 1:
6308
6473
  return
6309
6474
  element = self.wait_for_element_present(selector)
@@ -6441,6 +6606,34 @@ class BaseCase(unittest.TestCase):
6441
6606
  self.execute_script(scroll_script)
6442
6607
  time.sleep(0.012)
6443
6608
 
6609
+ def scroll_by_y(self, y):
6610
+ """Scrolls page by y pixels."""
6611
+ self.__check_scope()
6612
+ y = int(y)
6613
+ if self.__is_cdp_swap_needed():
6614
+ self.cdp.scroll_by_y(y)
6615
+ return
6616
+ scroll_script = "window.scrollBy(0, %s);" % y
6617
+ with suppress(Exception):
6618
+ self.execute_script(scroll_script)
6619
+ time.sleep(0.012)
6620
+
6621
+ def scroll_up(self, amount=25):
6622
+ """Scrolls up as a percentage of the page."""
6623
+ if self.__is_cdp_swap_needed():
6624
+ self.cdp.scroll_up(amount)
6625
+ return
6626
+ amount = self.get_window_size()["height"] * amount / 100
6627
+ self.execute_script("window.scrollBy(0, -%s);" % amount)
6628
+
6629
+ def scroll_down(self, amount=25):
6630
+ """Scrolls down as a percentage of the page."""
6631
+ if self.__is_cdp_swap_needed():
6632
+ self.cdp.scroll_down(amount)
6633
+ return
6634
+ amount = self.get_window_size()["height"] * amount / 100
6635
+ self.execute_script("window.scrollBy(0, %s);" % amount)
6636
+
6444
6637
  def click_xpath(self, xpath):
6445
6638
  """Technically, self.click() automatically detects xpath selectors,
6446
6639
  so self.click_xpath() is just a longer name for the same action."""
@@ -7184,21 +7377,6 @@ class BaseCase(unittest.TestCase):
7184
7377
  with pip_find_lock:
7185
7378
  with suppress(Exception):
7186
7379
  shared_utils.make_writable(constants.PipInstall.FINDLOCK)
7187
- if sys.version_info < (3, 9):
7188
- # Fix bug in newer cryptography for Python 3.7 and 3.8:
7189
- # "pyo3_runtime.PanicException: Python API call failed"
7190
- try:
7191
- import cryptography
7192
- if cryptography.__version__ != "39.0.2":
7193
- del cryptography # To get newer ver
7194
- shared_utils.pip_install(
7195
- "cryptography", version="39.0.2"
7196
- )
7197
- import cryptography
7198
- except Exception:
7199
- shared_utils.pip_install(
7200
- "cryptography", version="39.0.2"
7201
- )
7202
7380
  try:
7203
7381
  from pdfminer.high_level import extract_text
7204
7382
  except Exception:
@@ -7239,6 +7417,7 @@ class BaseCase(unittest.TestCase):
7239
7417
  page_search = [page]
7240
7418
  else:
7241
7419
  page_search = None
7420
+ logging.getLogger("pdfminer").setLevel(logging.ERROR)
7242
7421
  pdf_text = extract_text(
7243
7422
  file_path,
7244
7423
  password="",
@@ -7489,7 +7668,11 @@ class BaseCase(unittest.TestCase):
7489
7668
  destination_folder = constants.Files.DOWNLOADS_FOLDER
7490
7669
  if not os.path.exists(destination_folder):
7491
7670
  os.makedirs(destination_folder)
7492
- page_utils._download_file_to(file_url, destination_folder)
7671
+ agent = self.get_user_agent()
7672
+ headers = {"user-agent": agent}
7673
+ page_utils._download_file_to(
7674
+ file_url, destination_folder, headers=headers
7675
+ )
7493
7676
  if self.recorder_mode and self.__current_url_is_recordable():
7494
7677
  if self.get_session_storage_item("pause_recorder") == "no":
7495
7678
  time_stamp = self.execute_script("return Date.now();")
@@ -7513,8 +7696,10 @@ class BaseCase(unittest.TestCase):
7513
7696
  destination_folder = constants.Files.DOWNLOADS_FOLDER
7514
7697
  if not os.path.exists(destination_folder):
7515
7698
  os.makedirs(destination_folder)
7699
+ agent = self.get_user_agent()
7700
+ headers = {"user-agent": agent}
7516
7701
  page_utils._download_file_to(
7517
- file_url, destination_folder, new_file_name
7702
+ file_url, destination_folder, new_file_name, headers=headers
7518
7703
  )
7519
7704
 
7520
7705
  def save_data_as(self, data, file_name, destination_folder=None):
@@ -7546,6 +7731,47 @@ class BaseCase(unittest.TestCase):
7546
7731
  folder = constants.Files.DOWNLOADS_FOLDER
7547
7732
  return page_utils._get_file_data(folder, file_name)
7548
7733
 
7734
+ def print_to_pdf(self, name, folder=None):
7735
+ """Saves the current page as a PDF.
7736
+ If no folder is specified, uses the folder where pytest was called.
7737
+ If the folder provided doesn't exist, it will get created.
7738
+ @Params
7739
+ name - The name to give the PDF file. Must end in ".pdf".
7740
+ folder - The directory where you want to save the PDF."""
7741
+ import base64
7742
+ from selenium.webdriver.common.print_page_options import PrintOptions
7743
+
7744
+ if not name.lower().endswith(".pdf"):
7745
+ raise Exception('PDF name {%s} must end in ".pdf"!)' % name)
7746
+ download_file_lock = fasteners.InterProcessLock(
7747
+ constants.MultiBrowser.DOWNLOAD_FILE_LOCK
7748
+ )
7749
+ if self.__is_cdp_swap_needed():
7750
+ with download_file_lock:
7751
+ with suppress(Exception):
7752
+ shared_utils.make_writable(
7753
+ constants.MultiBrowser.DOWNLOAD_FILE_LOCK
7754
+ )
7755
+ if folder and not os.path.exists(folder):
7756
+ os.makedirs(folder)
7757
+ self.cdp.print_to_pdf(name, folder)
7758
+ return
7759
+ self.wait_for_ready_state_complete()
7760
+ print_options = PrintOptions()
7761
+ pdf_base64 = self.driver.print_page(print_options)
7762
+ with download_file_lock:
7763
+ with suppress(Exception):
7764
+ shared_utils.make_writable(
7765
+ constants.MultiBrowser.DOWNLOAD_FILE_LOCK
7766
+ )
7767
+ if folder and not os.path.exists(folder):
7768
+ os.makedirs(folder)
7769
+ filename = name
7770
+ if folder:
7771
+ filename = os.path.join(folder, name)
7772
+ with open(filename, "wb") as f:
7773
+ f.write(base64.b64decode(pdf_base64))
7774
+
7549
7775
  def get_downloads_folder(self):
7550
7776
  """Returns the path of the SeleniumBase "downloaded_files/" folder.
7551
7777
  Calling self.download_file(file_url) will put that file in here.
@@ -8399,33 +8625,9 @@ class BaseCase(unittest.TestCase):
8399
8625
 
8400
8626
  def get_mfa_code(self, totp_key=None):
8401
8627
  """Same as get_totp_code() and get_google_auth_password().
8402
- Returns a time-based one-time password based on the
8403
- Google Authenticator algorithm for multi-factor authentication.
8404
- If the "totp_key" is not specified, this method defaults
8405
- to using the one provided in [seleniumbase/config/settings.py].
8406
- Google Authenticator codes expire & change at 30-sec intervals.
8407
- If the fetched password expires in the next 1.2 seconds, waits
8408
- for a new one before returning it (may take up to 1.2 seconds).
8409
- See https://pyotp.readthedocs.io/en/latest/ for details."""
8410
- import pyotp
8411
-
8412
- if not totp_key:
8413
- totp_key = settings.TOTP_KEY
8414
-
8415
- epoch_interval = time.time() / 30.0
8416
- cycle_lifespan = float(epoch_interval) - int(epoch_interval)
8417
- if float(cycle_lifespan) > 0.96:
8418
- # Password expires in the next 1.2 seconds. Wait for a new one.
8419
- for i in range(30):
8420
- time.sleep(0.04)
8421
- epoch_interval = time.time() / 30.0
8422
- cycle_lifespan = float(epoch_interval) - int(epoch_interval)
8423
- if not float(cycle_lifespan) > 0.96:
8424
- # The new password cycle has begun
8425
- break
8426
-
8427
- totp = pyotp.TOTP(totp_key)
8428
- return str(totp.now())
8628
+ Returns a time-based one-time password based on the Google
8629
+ Authenticator algorithm for multi-factor authentication."""
8630
+ return shared_utils.get_mfa_code(totp_key)
8429
8631
 
8430
8632
  def enter_mfa_code(
8431
8633
  self, selector, totp_key=None, by="css selector", timeout=None
@@ -8609,6 +8811,9 @@ class BaseCase(unittest.TestCase):
8609
8811
  if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
8610
8812
  timeout = self.__get_new_timeout(timeout)
8611
8813
  selector, by = self.__recalculate_selector(selector, by)
8814
+ if self.__is_cdp_swap_needed():
8815
+ self.cdp.set_value(selector, text)
8816
+ return
8612
8817
  self.wait_for_ready_state_complete()
8613
8818
  element = page_actions.wait_for_element_present(
8614
8819
  self.driver, selector, by, timeout
@@ -8629,10 +8834,14 @@ class BaseCase(unittest.TestCase):
8629
8834
  if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
8630
8835
  timeout = self.__get_new_timeout(timeout)
8631
8836
  selector, by = self.__recalculate_selector(selector, by)
8632
- self.wait_for_ready_state_complete()
8633
- element = page_actions.wait_for_element_present(
8634
- self.driver, selector, by, timeout
8635
- )
8837
+ element = None
8838
+ if self.__is_cdp_swap_needed():
8839
+ element = self.cdp.select(selector, timeout=timeout)
8840
+ else:
8841
+ self.wait_for_ready_state_complete()
8842
+ element = page_actions.wait_for_element_present(
8843
+ self.driver, selector, by, timeout
8844
+ )
8636
8845
  if element.tag_name.lower() in ["input", "textarea"]:
8637
8846
  self.js_update_text(selector, text, by=by, timeout=timeout)
8638
8847
  return
@@ -8824,7 +9033,7 @@ class BaseCase(unittest.TestCase):
8824
9033
  self.__passed_then_skipped = True
8825
9034
  self.__will_be_skipped = True
8826
9035
  sb_config._results[test_id] = "Skipped"
8827
- if hasattr(self, "with_db_reporting") and self.with_db_reporting:
9036
+ if getattr(self, "with_db_reporting", None):
8828
9037
  if self.is_pytest:
8829
9038
  self.__skip_reason = reason
8830
9039
  else:
@@ -9095,9 +9304,9 @@ class BaseCase(unittest.TestCase):
9095
9304
  """Same as self.refresh_page()"""
9096
9305
  self.refresh_page()
9097
9306
 
9098
- def open_new_tab(self, switch_to=True):
9307
+ def open_new_tab(self, switch_to=True, **kwargs):
9099
9308
  """Same as self.open_new_window()"""
9100
- self.open_new_window(switch_to=switch_to)
9309
+ self.open_new_window(switch_to=switch_to, **kwargs)
9101
9310
 
9102
9311
  def switch_to_tab(self, tab, timeout=None):
9103
9312
  """Same as self.switch_to_window()
@@ -9114,85 +9323,50 @@ class BaseCase(unittest.TestCase):
9114
9323
  """Same as self.switch_to_newest_window()"""
9115
9324
  self.switch_to_newest_window()
9116
9325
 
9326
+ def save_as_html(self, name, folder=None):
9327
+ """Same as self.save_page_source()"""
9328
+ self.save_page_source(name, folder=folder)
9329
+
9117
9330
  def input(
9118
9331
  self, selector, text, by="css selector", timeout=None, retry=False
9119
9332
  ):
9120
9333
  """Same as self.update_text()"""
9121
- self.__check_scope()
9122
- if not timeout:
9123
- timeout = settings.LARGE_TIMEOUT
9124
- if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
9125
- timeout = self.__get_new_timeout(timeout)
9126
- selector, by = self.__recalculate_selector(selector, by)
9127
9334
  self.update_text(selector, text, by=by, timeout=timeout, retry=retry)
9128
9335
 
9129
9336
  def fill(
9130
9337
  self, selector, text, by="css selector", timeout=None, retry=False
9131
9338
  ):
9132
9339
  """Same as self.update_text()"""
9133
- self.__check_scope()
9134
- if not timeout:
9135
- timeout = settings.LARGE_TIMEOUT
9136
- if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
9137
- timeout = self.__get_new_timeout(timeout)
9138
- selector, by = self.__recalculate_selector(selector, by)
9139
9340
  self.update_text(selector, text, by=by, timeout=timeout, retry=retry)
9140
9341
 
9141
9342
  def write(
9142
9343
  self, selector, text, by="css selector", timeout=None, retry=False
9143
9344
  ):
9144
9345
  """Same as self.update_text()"""
9145
- self.__check_scope()
9146
- if not timeout:
9147
- timeout = settings.LARGE_TIMEOUT
9148
- if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
9149
- timeout = self.__get_new_timeout(timeout)
9150
- selector, by = self.__recalculate_selector(selector, by)
9151
9346
  self.update_text(selector, text, by=by, timeout=timeout, retry=retry)
9152
9347
 
9153
9348
  def click_link(self, link_text, timeout=None):
9154
9349
  """Same as self.click_link_text()"""
9155
- self.__check_scope()
9156
- if not timeout:
9157
- timeout = settings.SMALL_TIMEOUT
9158
- if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
9159
- timeout = self.__get_new_timeout(timeout)
9160
9350
  self.click_link_text(link_text, timeout=timeout)
9161
9351
 
9162
9352
  def click_partial_link(self, partial_link_text, timeout=None):
9163
9353
  """Same as self.click_partial_link_text()"""
9164
- self.__check_scope()
9165
- if not timeout:
9166
- timeout = settings.SMALL_TIMEOUT
9167
- if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
9168
- timeout = self.__get_new_timeout(timeout)
9169
9354
  self.click_partial_link_text(partial_link_text, timeout=timeout)
9170
9355
 
9171
9356
  def right_click(self, selector, by="css selector", timeout=None):
9172
9357
  """Same as self.context_click()"""
9173
- self.__check_scope()
9174
- if not timeout:
9175
- timeout = settings.SMALL_TIMEOUT
9176
- if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
9177
- timeout = self.__get_new_timeout(timeout)
9178
9358
  self.context_click(selector, by=by, timeout=timeout)
9179
9359
 
9360
+ def hover_element(self, selector, by="css selector", timeout=None):
9361
+ """Same as self.hover()"""
9362
+ return self.hover(selector, by=by, timeout=timeout)
9363
+
9180
9364
  def hover_on_element(self, selector, by="css selector", timeout=None):
9181
9365
  """Same as self.hover()"""
9182
- self.__check_scope()
9183
- if not timeout:
9184
- timeout = settings.SMALL_TIMEOUT
9185
- if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
9186
- timeout = self.__get_new_timeout(timeout)
9187
9366
  return self.hover(selector, by=by, timeout=timeout)
9188
9367
 
9189
9368
  def hover_over_element(self, selector, by="css selector", timeout=None):
9190
9369
  """Same as self.hover()"""
9191
- self.__check_scope()
9192
- if not timeout:
9193
- timeout = settings.SMALL_TIMEOUT
9194
- if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
9195
- timeout = self.__get_new_timeout(timeout)
9196
9370
  return self.hover(selector, by=by, timeout=timeout)
9197
9371
 
9198
9372
  def wait_for_element_visible(
@@ -9517,7 +9691,7 @@ class BaseCase(unittest.TestCase):
9517
9691
  To force a print during multithreaded tests, use: "sys.stderr.write()".
9518
9692
  To print without the new-line character end, use: "sys.stdout.write()".
9519
9693
  """
9520
- if hasattr(sb_config, "_multithreaded") and sb_config._multithreaded:
9694
+ if getattr(sb_config, "_multithreaded", None):
9521
9695
  if not isinstance(msg, str):
9522
9696
  with suppress(Exception):
9523
9697
  msg = str(msg)
@@ -9563,9 +9737,11 @@ class BaseCase(unittest.TestCase):
9563
9737
 
9564
9738
  def activate_messenger(self):
9565
9739
  self.__check_scope()
9566
- self._check_browser()
9740
+ if not self.__is_cdp_swap_needed():
9741
+ self._check_browser()
9567
9742
  js_utils.activate_messenger(self.driver)
9568
- self.wait_for_ready_state_complete()
9743
+ if not self.__is_cdp_swap_needed():
9744
+ self.wait_for_ready_state_complete()
9569
9745
 
9570
9746
  def set_messenger_theme(
9571
9747
  self, theme="default", location="default", max_messages="default"
@@ -9954,6 +10130,21 @@ class BaseCase(unittest.TestCase):
9954
10130
  if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
9955
10131
  timeout = self.__get_new_timeout(timeout)
9956
10132
  if self.__is_cdp_swap_needed():
10133
+ if self.demo_mode:
10134
+ selector, by = self.__recalculate_selector(
10135
+ selector, by, xp_ok=False
10136
+ )
10137
+ a_t = "ASSERT"
10138
+ if self._language != "English":
10139
+ from seleniumbase.fixtures.words import SD
10140
+
10141
+ a_t = SD.translate_assert(self._language)
10142
+ messenger_post = "<b>%s %s</b>: %s" % (
10143
+ a_t, by.upper(), selector
10144
+ )
10145
+ self.__highlight_with_assert_success(
10146
+ messenger_post, selector, by
10147
+ )
9957
10148
  self.cdp.assert_element(selector, timeout=timeout)
9958
10149
  return True
9959
10150
  if isinstance(selector, list):
@@ -10078,7 +10269,7 @@ class BaseCase(unittest.TestCase):
10078
10269
  text = self.__get_type_checked_text(text)
10079
10270
  selector, by = self.__recalculate_selector(selector, by)
10080
10271
  if self.__is_cdp_swap_needed():
10081
- return self.cdp.find_element(selector, timeout=timeout)
10272
+ return self.cdp.wait_for_text(text, selector, timeout=timeout)
10082
10273
  elif self.__is_shadow_selector(selector):
10083
10274
  return self.__wait_for_shadow_text_visible(text, selector, timeout)
10084
10275
  return page_actions.wait_for_text_visible(
@@ -10248,6 +10439,20 @@ class BaseCase(unittest.TestCase):
10248
10439
  messenger_post, selector, by
10249
10440
  )
10250
10441
  elif self.__is_cdp_swap_needed():
10442
+ if self.demo_mode:
10443
+ a_t = "ASSERT TEXT"
10444
+ i_n = "in"
10445
+ if self._language != "English":
10446
+ from seleniumbase.fixtures.words import SD
10447
+
10448
+ a_t = SD.translate_assert_text(self._language)
10449
+ i_n = SD.translate_in(self._language)
10450
+ messenger_post = "<b>%s</b>: {%s} %s %s: %s" % (
10451
+ a_t, text, i_n, by.upper(), selector
10452
+ )
10453
+ self.__highlight_with_assert_success(
10454
+ messenger_post, selector, by
10455
+ )
10251
10456
  self.cdp.assert_text(text, selector, timeout=timeout)
10252
10457
  return True
10253
10458
  elif self.__is_shadow_selector(selector):
@@ -10435,6 +10640,8 @@ class BaseCase(unittest.TestCase):
10435
10640
  timeout = settings.LARGE_TIMEOUT
10436
10641
  if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
10437
10642
  timeout = self.__get_new_timeout(timeout)
10643
+ if self.__is_cdp_swap_needed():
10644
+ return self.cdp.find_element_by_text(text=link_text, tag_name="a")
10438
10645
  return self.wait_for_element_visible(
10439
10646
  link_text, by="link text", timeout=timeout
10440
10647
  )
@@ -10981,7 +11188,7 @@ class BaseCase(unittest.TestCase):
10981
11188
  return # Skip the rest when deferred visual asserts are used
10982
11189
  the_html = visual_helper.get_sbs_html()
10983
11190
  file_path = os.path.join(test_logpath, constants.SideBySide.HTML_FILE)
10984
- out_file = codecs.open(file_path, "w+", encoding="utf-8")
11191
+ out_file = open(file_path, mode="w+", encoding="utf-8")
10985
11192
  out_file.writelines(the_html)
10986
11193
  out_file.close()
10987
11194
 
@@ -11141,16 +11348,16 @@ class BaseCase(unittest.TestCase):
11141
11348
  self.save_screenshot(
11142
11349
  baseline_png, visual_baseline_path, selector="body"
11143
11350
  )
11144
- out_file = codecs.open(page_url_file, "w+", encoding="utf-8")
11351
+ out_file = open(page_url_file, mode="w+", encoding="utf-8")
11145
11352
  out_file.writelines(page_url)
11146
11353
  out_file.close()
11147
- out_file = codecs.open(level_1_file, "w+", encoding="utf-8")
11354
+ out_file = open(level_1_file, mode="w+", encoding="utf-8")
11148
11355
  out_file.writelines(json.dumps(level_1))
11149
11356
  out_file.close()
11150
- out_file = codecs.open(level_2_file, "w+", encoding="utf-8")
11357
+ out_file = open(level_2_file, mode="w+", encoding="utf-8")
11151
11358
  out_file.writelines(json.dumps(level_2))
11152
11359
  out_file.close()
11153
- out_file = codecs.open(level_3_file, "w+", encoding="utf-8")
11360
+ out_file = open(level_3_file, mode="w+", encoding="utf-8")
11154
11361
  out_file.writelines(json.dumps(level_3))
11155
11362
  out_file.close()
11156
11363
 
@@ -11289,7 +11496,7 @@ class BaseCase(unittest.TestCase):
11289
11496
  alpha_n_d_name = "".join([x if x.isalnum() else "_" for x in name])
11290
11497
  side_by_side_name = "side_by_side_%s.html" % alpha_n_d_name
11291
11498
  file_path = os.path.join(test_logpath, side_by_side_name)
11292
- out_file = codecs.open(file_path, "w+", encoding="utf-8")
11499
+ out_file = open(file_path, mode="w+", encoding="utf-8")
11293
11500
  out_file.writelines(the_html)
11294
11501
  out_file.close()
11295
11502
 
@@ -11312,7 +11519,14 @@ class BaseCase(unittest.TestCase):
11312
11519
 
11313
11520
  def __is_cdp_swap_needed(self):
11314
11521
  """If the driver is disconnected, use a CDP method when available."""
11315
- return shared_utils.is_cdp_swap_needed(self.driver)
11522
+ cdp_swap_needed = shared_utils.is_cdp_swap_needed(self.driver)
11523
+ if cdp_swap_needed:
11524
+ if not self.cdp:
11525
+ self.cdp = self.driver.cdp
11526
+ self.undetectable = True
11527
+ return True
11528
+ else:
11529
+ return False
11316
11530
 
11317
11531
  ############
11318
11532
 
@@ -11974,7 +12188,7 @@ class BaseCase(unittest.TestCase):
11974
12188
  with suppress(Exception):
11975
12189
  os.makedirs(saved_presentations_folder)
11976
12190
  file_path = os.path.join(saved_presentations_folder, filename)
11977
- out_file = codecs.open(file_path, "w+", encoding="utf-8")
12191
+ out_file = open(file_path, mode="w+", encoding="utf-8")
11978
12192
  out_file.writelines(the_html)
11979
12193
  out_file.close()
11980
12194
  if self._output_file_saves:
@@ -12669,7 +12883,7 @@ class BaseCase(unittest.TestCase):
12669
12883
  with suppress(Exception):
12670
12884
  os.makedirs(saved_charts_folder)
12671
12885
  file_path = os.path.join(saved_charts_folder, filename)
12672
- out_file = codecs.open(file_path, "w+", encoding="utf-8")
12886
+ out_file = open(file_path, mode="w+", encoding="utf-8")
12673
12887
  out_file.writelines(the_html)
12674
12888
  out_file.close()
12675
12889
  if self._output_file_saves:
@@ -13728,7 +13942,7 @@ class BaseCase(unittest.TestCase):
13728
13942
  simulateClick(someLink);"""
13729
13943
  % css_selector
13730
13944
  )
13731
- if hasattr(self, "recorder_mode") and self.recorder_mode:
13945
+ if getattr(self, "recorder_mode", None):
13732
13946
  self.save_recorded_actions()
13733
13947
  try:
13734
13948
  self.execute_script(script)
@@ -13776,7 +13990,7 @@ class BaseCase(unittest.TestCase):
13776
13990
  var someLink = arguments[0];
13777
13991
  simulateClick(someLink);"""
13778
13992
  )
13779
- if hasattr(self, "recorder_mode") and self.recorder_mode:
13993
+ if getattr(self, "recorder_mode", None):
13780
13994
  self.save_recorded_actions()
13781
13995
  try:
13782
13996
  self.execute_script(script, element)
@@ -13828,6 +14042,9 @@ class BaseCase(unittest.TestCase):
13828
14042
  timeout=None,
13829
14043
  center=None,
13830
14044
  ):
14045
+ if self.__is_cdp_swap_needed():
14046
+ self.cdp.click_with_offset(selector, x, y, center=center)
14047
+ return
13831
14048
  self.wait_for_ready_state_complete()
13832
14049
  if self.__needs_minimum_wait():
13833
14050
  time.sleep(0.14)
@@ -13905,7 +14122,7 @@ class BaseCase(unittest.TestCase):
13905
14122
  )
13906
14123
  raise Exception(message)
13907
14124
  except InvalidArgumentException:
13908
- if not self.browser == "chrome":
14125
+ if not self.is_chromium():
13909
14126
  raise
13910
14127
  chrome_version = self.driver.capabilities["browserVersion"]
13911
14128
  major_chrome_version = chrome_version.split(".")[0]
@@ -13980,7 +14197,7 @@ class BaseCase(unittest.TestCase):
13980
14197
  selector = self.convert_to_css_selector(selector, by=by)
13981
14198
  selector = self.__make_css_match_first_element_only(selector)
13982
14199
  click_script = """jQuery('%s')[0].click();""" % selector
13983
- if hasattr(self, "recorder_mode") and self.recorder_mode:
14200
+ if getattr(self, "recorder_mode", None):
13984
14201
  self.save_recorded_actions()
13985
14202
  self.safe_execute_script(click_script)
13986
14203
 
@@ -14136,8 +14353,7 @@ class BaseCase(unittest.TestCase):
14136
14353
  def __needs_minimum_wait(self):
14137
14354
  if (
14138
14355
  self.page_load_strategy == "none"
14139
- and hasattr(settings, "SKIP_JS_WAITS")
14140
- and settings.SKIP_JS_WAITS
14356
+ and getattr(settings, "SKIP_JS_WAITS", None)
14141
14357
  ):
14142
14358
  return True
14143
14359
  else:
@@ -14373,7 +14589,8 @@ class BaseCase(unittest.TestCase):
14373
14589
  sb_config._virtual_display = self._xvfb_display
14374
14590
  if "DISPLAY" not in os.environ.keys():
14375
14591
  print(
14376
- "\nX11 display failed! Will use regular xvfb!"
14592
+ "\n X11 display failed! Is Xvfb installed? "
14593
+ "\n Try this: `sudo apt install -y xvfb`"
14377
14594
  )
14378
14595
  self.__activate_standard_virtual_display()
14379
14596
  else:
@@ -14386,7 +14603,10 @@ class BaseCase(unittest.TestCase):
14386
14603
  print("\n" + str(e.msg))
14387
14604
  else:
14388
14605
  print(e)
14389
- print("\nX11 display failed! Will use regular xvfb!")
14606
+ print(
14607
+ "\n X11 display failed! Is Xvfb installed? "
14608
+ "\n Try this: `sudo apt install -y xvfb`"
14609
+ )
14390
14610
  self.__activate_standard_virtual_display()
14391
14611
  return
14392
14612
  pyautogui_is_installed = False
@@ -14394,11 +14614,11 @@ class BaseCase(unittest.TestCase):
14394
14614
  import pyautogui
14395
14615
  with suppress(Exception):
14396
14616
  use_pyautogui_ver = constants.PyAutoGUI.VER
14397
- if pyautogui.__version__ != use_pyautogui_ver:
14617
+ u_pv = shared_utils.make_version_tuple(use_pyautogui_ver)
14618
+ pv = shared_utils.make_version_tuple(pyautogui.__version__)
14619
+ if pv < u_pv:
14398
14620
  del pyautogui # To get newer ver
14399
- shared_utils.pip_install(
14400
- "pyautogui", version=use_pyautogui_ver
14401
- )
14621
+ shared_utils.pip_install("pyautogui", version="Latest")
14402
14622
  import pyautogui
14403
14623
  pyautogui_is_installed = True
14404
14624
  except Exception:
@@ -14407,9 +14627,7 @@ class BaseCase(unittest.TestCase):
14407
14627
  "Installing now..."
14408
14628
  )
14409
14629
  print("\n" + message)
14410
- shared_utils.pip_install(
14411
- "pyautogui", version=constants.PyAutoGUI.VER
14412
- )
14630
+ shared_utils.pip_install("pyautogui", version="Latest")
14413
14631
  import pyautogui
14414
14632
  pyautogui_is_installed = True
14415
14633
  if (
@@ -14467,10 +14685,7 @@ class BaseCase(unittest.TestCase):
14467
14685
 
14468
14686
  def __disable_beforeunload_as_needed(self):
14469
14687
  """Disables beforeunload as needed. Also resets frame_switch state."""
14470
- if (
14471
- hasattr(self, "_disable_beforeunload")
14472
- and self._disable_beforeunload
14473
- ):
14688
+ if getattr(self, "_disable_beforeunload", None):
14474
14689
  self.disable_beforeunload()
14475
14690
  if self.recorder_mode:
14476
14691
  try:
@@ -14522,7 +14737,7 @@ class BaseCase(unittest.TestCase):
14522
14737
  try:
14523
14738
  shadow_root = element.shadow_root
14524
14739
  except Exception:
14525
- if self.browser == "chrome":
14740
+ if self.is_chromium():
14526
14741
  chrome_dict = self.driver.capabilities["chrome"]
14527
14742
  chrome_dr_version = chrome_dict["chromedriverVersion"]
14528
14743
  chromedriver_version = chrome_dr_version.split(" ")[0]
@@ -15271,9 +15486,9 @@ class BaseCase(unittest.TestCase):
15271
15486
  self.__skip_reason = None
15272
15487
  self.testcase_manager.insert_testcase_data(data_payload)
15273
15488
  self.case_start_time = int(time.time() * 1000.0)
15274
- elif hasattr(self, "is_behave") and self.is_behave:
15489
+ elif getattr(self, "is_behave", None):
15275
15490
  self.__initialize_variables()
15276
- elif hasattr(self, "is_nosetest") and self.is_nosetest:
15491
+ elif getattr(self, "is_nosetest", None):
15277
15492
  pass # Setup performed in plugins for pynose
15278
15493
  else:
15279
15494
  # Pure Python run. (Eg. SB() and Driver() Managers)
@@ -15364,7 +15579,7 @@ class BaseCase(unittest.TestCase):
15364
15579
  "Invalid input for Mobile Emulator device metrics!\n"
15365
15580
  "Expecting a comma-separated string with integer values\n"
15366
15581
  "for Width/Height, and an int or float for Pixel-Ratio.\n"
15367
- 'Example: --metrics="411,731,3" '
15582
+ 'Example: --metrics="412,732,3" '
15368
15583
  )
15369
15584
  if len(metrics_list) != 3:
15370
15585
  raise Exception(exception_string)
@@ -15465,7 +15680,7 @@ class BaseCase(unittest.TestCase):
15465
15680
  )
15466
15681
  raise Exception(message)
15467
15682
 
15468
- if not hasattr(self, "is_nosetest") or not self.is_nosetest:
15683
+ if not getattr(self, "is_nosetest", None):
15469
15684
  # Xvfb Virtual Display activation for Linux
15470
15685
  self.__activate_virtual_display_as_needed()
15471
15686
 
@@ -15641,10 +15856,7 @@ class BaseCase(unittest.TestCase):
15641
15856
  self.__last_page_screenshot_png is for all screenshot log files."""
15642
15857
  SCREENSHOT_SKIPPED = constants.Warnings.SCREENSHOT_SKIPPED
15643
15858
  SCREENSHOT_UNDEFINED = constants.Warnings.SCREENSHOT_UNDEFINED
15644
- if (
15645
- hasattr(self, "no_screenshot_after_test")
15646
- and self.no_screenshot_after_test
15647
- ):
15859
+ if getattr(self, "no_screenshot_after_test", None):
15648
15860
  from seleniumbase.core import encoded_images
15649
15861
 
15650
15862
  NO_SCREENSHOT = encoded_images.get_no_screenshot_png()
@@ -15675,10 +15887,7 @@ class BaseCase(unittest.TestCase):
15675
15887
  ignore_test_time_limit=True,
15676
15888
  )
15677
15889
  try:
15678
- if (
15679
- hasattr(settings, "SCREENSHOT_WITH_BACKGROUND")
15680
- and settings.SCREENSHOT_WITH_BACKGROUND
15681
- ):
15890
+ if getattr(settings, "SCREENSHOT_WITH_BACKGROUND", None):
15682
15891
  self.__last_page_screenshot = (
15683
15892
  self.driver.get_screenshot_as_base64()
15684
15893
  )
@@ -15749,11 +15958,10 @@ class BaseCase(unittest.TestCase):
15749
15958
  exc_message = None
15750
15959
  if (
15751
15960
  hasattr(self, "_outcome")
15752
- and hasattr(self._outcome, "errors")
15753
- and self._outcome.errors
15961
+ and getattr(self._outcome, "errors", None)
15754
15962
  ):
15755
15963
  try:
15756
- exc_message = self._outcome.errors[0][1][1]
15964
+ exc_message = self._outcome.errors[-1][1][1]
15757
15965
  except Exception:
15758
15966
  exc_message = "(Unknown Exception)"
15759
15967
  else:
@@ -15838,8 +16046,7 @@ class BaseCase(unittest.TestCase):
15838
16046
  def __delay_driver_quit(self):
15839
16047
  delay_driver_quit = False
15840
16048
  if (
15841
- hasattr(self, "_using_sb_fixture")
15842
- and self._using_sb_fixture
16049
+ getattr(self, "_using_sb_fixture", None)
15843
16050
  and "--pdb" in sys.argv
15844
16051
  and self.__has_exception()
15845
16052
  and len(self._drivers_list) == 1
@@ -15895,14 +16102,22 @@ class BaseCase(unittest.TestCase):
15895
16102
  has_exception = False
15896
16103
  if hasattr(sys, "last_traceback") and sys.last_traceback is not None:
15897
16104
  has_exception = True
15898
- elif hasattr(self, "is_context_manager") and self.is_context_manager:
16105
+ elif getattr(self, "is_context_manager", None):
15899
16106
  if self.with_testing_base and self._has_failure:
15900
16107
  return True
15901
16108
  else:
15902
16109
  return False
15903
16110
  elif hasattr(self, "_outcome") and hasattr(self._outcome, "errors"):
15904
- if self._outcome.errors:
15905
- has_exception = True
16111
+ if python3_11_or_newer:
16112
+ if (
16113
+ self._outcome.errors
16114
+ and self._outcome.errors[-1]
16115
+ and self._outcome.errors[-1][1]
16116
+ ):
16117
+ has_exception = True
16118
+ else:
16119
+ if self._outcome.errors:
16120
+ has_exception = True
15906
16121
  else:
15907
16122
  has_exception = sys.exc_info()[1] is not None
15908
16123
  if self.__will_be_skipped and hasattr(self, "_using_sb_fixture"):
@@ -15911,7 +16126,7 @@ class BaseCase(unittest.TestCase):
15911
16126
 
15912
16127
  def __get_test_id(self):
15913
16128
  """The id used in various places such as the test log path."""
15914
- if hasattr(self, "is_behave") and self.is_behave:
16129
+ if getattr(self, "is_behave", None):
15915
16130
  file_name = sb_config.behave_scenario.filename
15916
16131
  file_name = file_name.replace("/", ".").replace("\\", ".")
15917
16132
  scenario_name = sb_config.behave_scenario.name
@@ -15921,7 +16136,7 @@ class BaseCase(unittest.TestCase):
15921
16136
  scenario_name = scenario_name.replace(" ", "_")
15922
16137
  test_id = "%s.%s" % (file_name, scenario_name)
15923
16138
  return test_id
15924
- elif hasattr(self, "is_context_manager") and self.is_context_manager:
16139
+ elif getattr(self, "is_context_manager", None):
15925
16140
  if hasattr(self, "_manager_saved_id"):
15926
16141
  self.__saved_id = self._manager_saved_id
15927
16142
  if self.__saved_id:
@@ -15933,7 +16148,7 @@ class BaseCase(unittest.TestCase):
15933
16148
  import traceback
15934
16149
  stack_base = traceback.format_stack()[0].split(os.sep)[-1]
15935
16150
  test_base = stack_base.split(", in ")[0]
15936
- if hasattr(self, "cm_filename") and self.cm_filename:
16151
+ if getattr(self, "cm_filename", None):
15937
16152
  filename = self.cm_filename
15938
16153
  else:
15939
16154
  filename = test_base.split('"')[0]
@@ -15948,12 +16163,12 @@ class BaseCase(unittest.TestCase):
15948
16163
  )
15949
16164
  if self._sb_test_identifier and len(str(self._sb_test_identifier)) > 6:
15950
16165
  test_id = self._sb_test_identifier
15951
- elif hasattr(self, "_using_sb_fixture") and self._using_sb_fixture:
16166
+ elif getattr(self, "_using_sb_fixture", None):
15952
16167
  test_id = sb_config._latest_display_id
15953
16168
  test_id = test_id.replace(".py::", ".").replace("::", ".")
15954
16169
  test_id = test_id.replace("/", ".").replace("\\", ".")
15955
16170
  test_id = test_id.replace(" ", "_")
15956
- # Linux filename length limit for `codecs.open(filename)` = 255
16171
+ # Linux filename length limit for `open(filename)` = 255
15957
16172
  # 255 - len("latest_logs/") - len("/basic_test_info.txt") = 223
15958
16173
  if len(test_id) <= 223:
15959
16174
  return test_id
@@ -15970,7 +16185,7 @@ class BaseCase(unittest.TestCase):
15970
16185
  return full_name.split("] ")[0] + "]"
15971
16186
  else:
15972
16187
  return full_name.split(" ")[0]
15973
- if hasattr(self, "is_behave") and self.is_behave:
16188
+ if getattr(self, "is_behave", None):
15974
16189
  return self.__get_test_id()
15975
16190
  test_id = "%s.%s.%s" % (
15976
16191
  self.__class__.__module__.split(".")[-1],
@@ -15991,7 +16206,7 @@ class BaseCase(unittest.TestCase):
15991
16206
  return full_name.split("] ")[0] + "]"
15992
16207
  else:
15993
16208
  return full_name.split(" ")[0]
15994
- if hasattr(self, "is_behave") and self.is_behave:
16209
+ if getattr(self, "is_behave", None):
15995
16210
  file_name = sb_config.behave_scenario.filename
15996
16211
  line_num = sb_config.behave_line_num
15997
16212
  scenario_name = sb_config.behave_scenario.name
@@ -16024,7 +16239,7 @@ class BaseCase(unittest.TestCase):
16024
16239
  if "PYTEST_CURRENT_TEST" in os.environ:
16025
16240
  test_id = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]
16026
16241
  filename = test_id.split("::")[0].split("/")[-1]
16027
- elif hasattr(self, "is_behave") and self.is_behave:
16242
+ elif getattr(self, "is_behave", None):
16028
16243
  filename = sb_config.behave_scenario.filename
16029
16244
  filename = filename.split("/")[-1].split("\\")[-1]
16030
16245
  else:
@@ -16060,8 +16275,7 @@ class BaseCase(unittest.TestCase):
16060
16275
  sb_config._pdb_failure = True
16061
16276
  elif (
16062
16277
  self.is_pytest
16063
- and hasattr(sb_config, "_pdb_failure")
16064
- and sb_config._pdb_failure
16278
+ and getattr(sb_config, "_pdb_failure", None)
16065
16279
  and not has_exception
16066
16280
  ):
16067
16281
  return # Handle case where "pytest --pdb" marks failures as Passed
@@ -16231,7 +16445,7 @@ class BaseCase(unittest.TestCase):
16231
16445
  dash_pie = json.dumps(sb_config._saved_dashboard_pie)
16232
16446
  dash_pie_loc = constants.Dashboard.DASH_PIE
16233
16447
  pie_path = os.path.join(abs_path, dash_pie_loc)
16234
- pie_file = codecs.open(pie_path, "w+", encoding="utf-8")
16448
+ pie_file = open(pie_path, mode="w+", encoding="utf-8")
16235
16449
  pie_file.writelines(dash_pie)
16236
16450
  pie_file.close()
16237
16451
  DASH_PIE_PNG_1 = constants.Dashboard.get_dash_pie_1()
@@ -16391,7 +16605,7 @@ class BaseCase(unittest.TestCase):
16391
16605
  )
16392
16606
  abs_path = os.path.abspath(".")
16393
16607
  file_path = os.path.join(abs_path, "dashboard.html")
16394
- out_file = codecs.open(file_path, "w+", encoding="utf-8")
16608
+ out_file = open(file_path, mode="w+", encoding="utf-8")
16395
16609
  out_file.writelines(the_html)
16396
16610
  out_file.close()
16397
16611
  sb_config._dash_html = the_html
@@ -16404,7 +16618,7 @@ class BaseCase(unittest.TestCase):
16404
16618
  dash_json = json.dumps((_results, _display_id, _rt, _tlp, d_stats))
16405
16619
  dash_json_loc = constants.Dashboard.DASH_JSON
16406
16620
  dash_jsonpath = os.path.join(abs_path, dash_json_loc)
16407
- dash_json_file = codecs.open(dash_jsonpath, "w+", encoding="utf-8")
16621
+ dash_json_file = open(dash_jsonpath, mode="w+", encoding="utf-8")
16408
16622
  dash_json_file.writelines(dash_json)
16409
16623
  dash_json_file.close()
16410
16624
 
@@ -16455,7 +16669,7 @@ class BaseCase(unittest.TestCase):
16455
16669
  self.__check_scope()
16456
16670
  except Exception:
16457
16671
  return
16458
- if hasattr(self, "recorder_mode") and self.recorder_mode:
16672
+ if getattr(self, "recorder_mode", None):
16459
16673
  # In case tearDown() leaves the origin, save actions first.
16460
16674
  self.save_recorded_actions()
16461
16675
  if (
@@ -16650,7 +16864,7 @@ class BaseCase(unittest.TestCase):
16650
16864
  if not hasattr(self, "_using_sb_fixture") and self.__called_teardown:
16651
16865
  # This test already called tearDown()
16652
16866
  return
16653
- if hasattr(self, "recorder_mode") and self.recorder_mode:
16867
+ if getattr(self, "recorder_mode", None):
16654
16868
  page_actions._reconnect_if_disconnected(self.driver)
16655
16869
  try:
16656
16870
  self.__process_recorded_actions()
@@ -16885,7 +17099,7 @@ class BaseCase(unittest.TestCase):
16885
17099
  self.testcase_manager.update_testcase_log_url(data_payload)
16886
17100
  else:
16887
17101
  # (Pynose / Behave / Pure Python)
16888
- if hasattr(self, "is_behave") and self.is_behave:
17102
+ if getattr(self, "is_behave", None):
16889
17103
  if sb_config.behave_scenario.status.name == "failed":
16890
17104
  has_exception = True
16891
17105
  sb_config._has_exception = True
@@ -16945,8 +17159,8 @@ class BaseCase(unittest.TestCase):
16945
17159
  self._last_page_url = self.get_current_url()
16946
17160
  except Exception:
16947
17161
  self._last_page_url = "(Error: Unknown URL)"
16948
- if hasattr(self, "is_behave") and self.is_behave and has_exception:
16949
- if hasattr(sb_config, "pdb_option") and sb_config.pdb_option:
17162
+ if getattr(self, "is_behave", None) and has_exception:
17163
+ if getattr(sb_config, "pdb_option", None):
16950
17164
  if (
16951
17165
  hasattr(sb_config, "behave_step")
16952
17166
  and hasattr(sb_config.behave_step, "exc_traceback")
@@ -16954,24 +17168,14 @@ class BaseCase(unittest.TestCase):
16954
17168
  self.__activate_behave_post_mortem_debug_mode()
16955
17169
  if self._final_debug:
16956
17170
  self.__activate_debug_mode_in_teardown()
16957
- elif (
16958
- hasattr(sb_config, "_do_sb_post_mortem")
16959
- and sb_config._do_sb_post_mortem
16960
- ):
17171
+ elif getattr(sb_config, "_do_sb_post_mortem", None):
16961
17172
  self.__activate_sb_mgr_post_mortem_debug_mode()
16962
- elif (
16963
- hasattr(sb_config, "_do_sb_final_trace")
16964
- and sb_config._do_sb_final_trace
16965
- ):
17173
+ elif getattr(sb_config, "_do_sb_final_trace", None):
16966
17174
  self.__activate_debug_mode_in_teardown()
16967
17175
  # (Pynose / Behave / Pure Python) Close all open browser windows
16968
17176
  self.__quit_all_drivers()
16969
17177
  # Resume tearDown() for all test runners, (Pytest / Pynose / Behave)
16970
- if (
16971
- hasattr(self, "_xvfb_display")
16972
- and self._xvfb_display
16973
- and not self._reuse_session
16974
- ):
17178
+ if getattr(self, "_xvfb_display", None) and not self._reuse_session:
16975
17179
  # Stop the Xvfb virtual display launched from BaseCase
16976
17180
  try:
16977
17181
  if hasattr(self._xvfb_display, "stop"):
@@ -16983,16 +17187,9 @@ class BaseCase(unittest.TestCase):
16983
17187
  except Exception:
16984
17188
  pass
16985
17189
  if (
16986
- hasattr(sb_config, "_virtual_display")
16987
- and sb_config._virtual_display
17190
+ getattr(sb_config, "_virtual_display", None)
16988
17191
  and hasattr(sb_config._virtual_display, "stop")
16989
- and (
16990
- not hasattr(sb_config, "reuse_session")
16991
- or (
16992
- hasattr(sb_config, "reuse_session")
16993
- and not sb_config.reuse_session
16994
- )
16995
- )
17192
+ and not getattr(sb_config, "reuse_session", None)
16996
17193
  ):
16997
17194
  # CDP Mode may launch a 2nd Xvfb virtual display
16998
17195
  try: