seleniumbase 4.24.11__py3-none-any.whl → 4.33.15__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 (78) hide show
  1. sbase/__init__.py +1 -0
  2. sbase/steps.py +7 -0
  3. seleniumbase/__init__.py +16 -7
  4. seleniumbase/__version__.py +1 -1
  5. seleniumbase/behave/behave_sb.py +97 -32
  6. seleniumbase/common/decorators.py +16 -7
  7. seleniumbase/config/proxy_list.py +3 -3
  8. seleniumbase/config/settings.py +4 -0
  9. seleniumbase/console_scripts/logo_helper.py +47 -8
  10. seleniumbase/console_scripts/run.py +345 -335
  11. seleniumbase/console_scripts/sb_behave_gui.py +5 -12
  12. seleniumbase/console_scripts/sb_caseplans.py +6 -13
  13. seleniumbase/console_scripts/sb_commander.py +5 -12
  14. seleniumbase/console_scripts/sb_install.py +62 -54
  15. seleniumbase/console_scripts/sb_mkchart.py +13 -20
  16. seleniumbase/console_scripts/sb_mkdir.py +11 -17
  17. seleniumbase/console_scripts/sb_mkfile.py +69 -43
  18. seleniumbase/console_scripts/sb_mkpres.py +13 -20
  19. seleniumbase/console_scripts/sb_mkrec.py +88 -21
  20. seleniumbase/console_scripts/sb_objectify.py +30 -30
  21. seleniumbase/console_scripts/sb_print.py +5 -12
  22. seleniumbase/console_scripts/sb_recorder.py +16 -11
  23. seleniumbase/core/browser_launcher.py +1658 -221
  24. seleniumbase/core/log_helper.py +42 -27
  25. seleniumbase/core/mysql.py +1 -4
  26. seleniumbase/core/proxy_helper.py +35 -30
  27. seleniumbase/core/recorder_helper.py +24 -5
  28. seleniumbase/core/sb_cdp.py +1951 -0
  29. seleniumbase/core/sb_driver.py +162 -8
  30. seleniumbase/core/settings_parser.py +6 -0
  31. seleniumbase/core/style_sheet.py +10 -0
  32. seleniumbase/extensions/recorder.zip +0 -0
  33. seleniumbase/fixtures/base_case.py +1225 -614
  34. seleniumbase/fixtures/constants.py +10 -1
  35. seleniumbase/fixtures/js_utils.py +171 -144
  36. seleniumbase/fixtures/page_actions.py +177 -13
  37. seleniumbase/fixtures/page_utils.py +25 -53
  38. seleniumbase/fixtures/shared_utils.py +97 -11
  39. seleniumbase/js_code/active_css_js.py +1 -1
  40. seleniumbase/js_code/recorder_js.py +1 -1
  41. seleniumbase/plugins/base_plugin.py +2 -3
  42. seleniumbase/plugins/driver_manager.py +340 -65
  43. seleniumbase/plugins/pytest_plugin.py +276 -47
  44. seleniumbase/plugins/sb_manager.py +412 -99
  45. seleniumbase/plugins/selenium_plugin.py +122 -17
  46. seleniumbase/translate/translator.py +0 -7
  47. seleniumbase/undetected/__init__.py +59 -52
  48. seleniumbase/undetected/cdp.py +0 -1
  49. seleniumbase/undetected/cdp_driver/__init__.py +1 -0
  50. seleniumbase/undetected/cdp_driver/_contradict.py +110 -0
  51. seleniumbase/undetected/cdp_driver/browser.py +829 -0
  52. seleniumbase/undetected/cdp_driver/cdp_util.py +458 -0
  53. seleniumbase/undetected/cdp_driver/config.py +334 -0
  54. seleniumbase/undetected/cdp_driver/connection.py +639 -0
  55. seleniumbase/undetected/cdp_driver/element.py +1168 -0
  56. seleniumbase/undetected/cdp_driver/tab.py +1323 -0
  57. seleniumbase/undetected/dprocess.py +4 -7
  58. seleniumbase/undetected/options.py +6 -8
  59. seleniumbase/undetected/patcher.py +11 -13
  60. seleniumbase/undetected/reactor.py +0 -1
  61. seleniumbase/undetected/webelement.py +16 -3
  62. {seleniumbase-4.24.11.dist-info → seleniumbase-4.33.15.dist-info}/LICENSE +1 -1
  63. {seleniumbase-4.24.11.dist-info → seleniumbase-4.33.15.dist-info}/METADATA +299 -252
  64. {seleniumbase-4.24.11.dist-info → seleniumbase-4.33.15.dist-info}/RECORD +67 -69
  65. {seleniumbase-4.24.11.dist-info → seleniumbase-4.33.15.dist-info}/WHEEL +1 -1
  66. sbase/ReadMe.txt +0 -2
  67. seleniumbase/ReadMe.md +0 -25
  68. seleniumbase/common/ReadMe.md +0 -71
  69. seleniumbase/console_scripts/ReadMe.md +0 -731
  70. seleniumbase/drivers/ReadMe.md +0 -27
  71. seleniumbase/extensions/ReadMe.md +0 -12
  72. seleniumbase/masterqa/ReadMe.md +0 -61
  73. seleniumbase/resources/ReadMe.md +0 -31
  74. seleniumbase/resources/favicon.ico +0 -0
  75. seleniumbase/utilities/selenium_grid/ReadMe.md +0 -84
  76. seleniumbase/utilities/selenium_ide/ReadMe.md +0 -111
  77. {seleniumbase-4.24.11.dist-info → seleniumbase-4.33.15.dist-info}/entry_points.txt +0 -0
  78. {seleniumbase-4.24.11.dist-info → seleniumbase-4.33.15.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ import os
4
4
  import pytest
5
5
  import sys
6
6
  import time
7
+ from contextlib import suppress
7
8
  from seleniumbase import config as sb_config
8
9
  from seleniumbase.config import settings
9
10
  from seleniumbase.core import log_helper
@@ -16,6 +17,7 @@ if sys.version_info >= (3, 11):
16
17
  python3_11_or_newer = True
17
18
  py311_patch2 = constants.PatchPy311.PATCH
18
19
  sys_argv = sys.argv
20
+ full_time = None
19
21
  pytest_plugins = ["pytester"] # Adds the "testdir" fixture
20
22
 
21
23
 
@@ -59,11 +61,14 @@ def pytest_addoption(parser):
59
61
  --binary-location=PATH (Set path of the Chromium browser binary to use.)
60
62
  --driver-version=VER (Set the chromedriver or uc_driver version to use.)
61
63
  --sjw (Skip JS Waits for readyState to be "complete" or Angular to load.)
64
+ --wfa (Wait for AngularJS to be done loading after specific web actions.)
62
65
  --pls=PLS (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".)
63
- --headless (Run tests in headless mode. The default arg on Linux OS.)
64
- --headless2 (Use the new headless mode, which supports extensions.)
66
+ --headless (The default headless mode. Linux uses this mode by default.)
67
+ --headless1 (Use Chrome's old headless mode. Fast, but has limitations.)
68
+ --headless2 (Use Chrome's new headless mode, which supports extensions.)
65
69
  --headed (Run tests in headed/GUI mode on Linux OS, where not default.)
66
70
  --xvfb (Run tests using the Xvfb virtual display server on Linux OS.)
71
+ --xvfb-metrics=STRING (Set Xvfb display size on Linux: "Width,Height".)
67
72
  --locale=LOCALE_CODE (Set the Language Locale Code for the web browser.)
68
73
  --interval=SECONDS (The autoplay interval for presentations & tour steps)
69
74
  --start-page=URL (The starting URL for the web browser when tests begin.)
@@ -81,10 +86,12 @@ def pytest_addoption(parser):
81
86
  --block-images (Block images from loading during tests.)
82
87
  --do-not-track (Indicate to websites that you don't want to be tracked.)
83
88
  --verify-delay=SECONDS (The delay before MasterQA verification checks.)
89
+ --ee / --esc-end (Lets the user end the current test via the ESC key.)
84
90
  --recorder (Enables the Recorder for turning browser actions into code.)
85
91
  --rec-behave (Same as Recorder Mode, but also generates behave-gherkin.)
86
92
  --rec-sleep (If the Recorder is enabled, also records self.sleep calls.)
87
93
  --rec-print (If the Recorder is enabled, prints output after tests end.)
94
+ --disable-cookies (Disable Cookies on websites. Pages might break!)
88
95
  --disable-js (Disable JavaScript on websites. Pages might break!)
89
96
  --disable-csp (Disable the Content Security Policy of websites.)
90
97
  --disable-ws (Disable Web Security on Chromium-based browsers.)
@@ -107,6 +114,7 @@ def pytest_addoption(parser):
107
114
  --rcs | --reuse-class-session (Reuse session for tests in class.)
108
115
  --crumbs (Delete all cookies between tests reusing a session.)
109
116
  --disable-beforeunload (Disable the "beforeunload" event on Chrome.)
117
+ --window-position=X,Y (Set the browser's starting window position.)
110
118
  --window-size=WIDTH,HEIGHT (Set the browser's starting window size.)
111
119
  --maximize (Start tests with the browser window maximized.)
112
120
  --screenshot (Save a screenshot at the end of each test.)
@@ -123,10 +131,6 @@ def pytest_addoption(parser):
123
131
  cr = ""
124
132
  if "linux" not in sys.platform:
125
133
  # This will be seen when typing "pytest --help" on the command line.
126
- if is_windows and hasattr(colorama, "just_fix_windows_console"):
127
- colorama.just_fix_windows_console()
128
- else:
129
- colorama.init(autoreset=True)
130
134
  c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX
131
135
  c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX
132
136
  c3 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX
@@ -357,6 +361,17 @@ def pytest_addoption(parser):
357
361
  and wait_for_angularjs(), which are part of many
358
362
  SeleniumBase methods for improving reliability.""",
359
363
  )
364
+ parser.addoption(
365
+ "--wfa",
366
+ "--wait_for_angularjs",
367
+ "--wait-for-angularjs",
368
+ action="store_true",
369
+ dest="wait_for_angularjs",
370
+ default=False,
371
+ help="""Add waiting for AngularJS. (The default setting
372
+ was changed to no longer wait for AngularJS to
373
+ finish loading as an extra JavaScript call.)""",
374
+ )
360
375
  parser.addoption(
361
376
  "--with-db_reporting",
362
377
  "--with-db-reporting",
@@ -684,6 +699,15 @@ def pytest_addoption(parser):
684
699
  UNLESS using a virtual display with Xvfb.
685
700
  Default: False on Mac/Windows. True on Linux.""",
686
701
  )
702
+ parser.addoption(
703
+ "--headless1",
704
+ action="store_true",
705
+ dest="headless1",
706
+ default=False,
707
+ help="""This option activates the old headless mode,
708
+ which is faster, but has limitations.
709
+ (May be phased out by Chrome in the future.)""",
710
+ )
687
711
  parser.addoption(
688
712
  "--headless2",
689
713
  action="store_true",
@@ -715,6 +739,17 @@ def pytest_addoption(parser):
715
739
  will no longer be enabled by default on Linux.
716
740
  Default: False. (Linux-ONLY!)""",
717
741
  )
742
+ parser.addoption(
743
+ "--xvfb-metrics",
744
+ "--xvfb_metrics",
745
+ action="store",
746
+ dest="xvfb_metrics",
747
+ default=None,
748
+ help="""Customize the Xvfb metrics (Width,Height) on Linux.
749
+ Format: A comma-separated string with the 2 values.
750
+ Examples: "1920,1080" or "1366,768" or "1024,768".
751
+ Default: None. (None: "1366,768". Min: "1024,768".)""",
752
+ )
718
753
  parser.addoption(
719
754
  "--locale_code",
720
755
  "--locale-code",
@@ -895,6 +930,16 @@ def pytest_addoption(parser):
895
930
  help="""Setting this overrides the default wait time
896
931
  before each MasterQA verification pop-up.""",
897
932
  )
933
+ parser.addoption(
934
+ "--esc-end",
935
+ "--esc_end",
936
+ "--ee",
937
+ action="store_true",
938
+ dest="esc_end",
939
+ default=False,
940
+ help="""End the current test early via the ESC key.
941
+ The test will be marked as skipped.""",
942
+ )
898
943
  parser.addoption(
899
944
  "--recorder",
900
945
  "--record",
@@ -945,6 +990,15 @@ def pytest_addoption(parser):
945
990
  help="""The option to disable JavaScript on web pages.
946
991
  Warning: Most web pages will stop working!""",
947
992
  )
993
+ parser.addoption(
994
+ "--disable_cookies",
995
+ "--disable-cookies",
996
+ action="store_true",
997
+ dest="disable_cookies",
998
+ default=False,
999
+ help="""The option to disable Cookies on web pages.
1000
+ Warning: Several pages may stop working!""",
1001
+ )
948
1002
  parser.addoption(
949
1003
  "--disable_csp",
950
1004
  "--disable-csp",
@@ -964,6 +1018,7 @@ def pytest_addoption(parser):
964
1018
  parser.addoption(
965
1019
  "--disable_ws",
966
1020
  "--disable-ws",
1021
+ "--dws",
967
1022
  "--disable-web-security",
968
1023
  action="store_true",
969
1024
  dest="disable_ws",
@@ -1206,6 +1261,17 @@ def pytest_addoption(parser):
1206
1261
  on Chromium browsers (Chrome or Edge).
1207
1262
  This is already the default Firefox option.""",
1208
1263
  )
1264
+ parser.addoption(
1265
+ "--window-position",
1266
+ "--window_position",
1267
+ action="store",
1268
+ dest="window_position",
1269
+ default=None,
1270
+ help="""The option to set the starting window x,y position
1271
+ Format: A comma-separated string with the 2 values.
1272
+ Example: "55,66"
1273
+ Default: None. (Will use default values if None)""",
1274
+ )
1209
1275
  parser.addoption(
1210
1276
  "--window-size",
1211
1277
  "--window_size",
@@ -1485,6 +1551,9 @@ def pytest_configure(config):
1485
1551
  sb_config.mobile_emulator = config.getoption("mobile_emulator")
1486
1552
  sb_config.device_metrics = config.getoption("device_metrics")
1487
1553
  sb_config.headless = config.getoption("headless")
1554
+ sb_config.headless1 = config.getoption("headless1")
1555
+ if sb_config.headless1:
1556
+ sb_config.headless = True
1488
1557
  sb_config.headless2 = config.getoption("headless2")
1489
1558
  if sb_config.headless2 and sb_config.browser == "firefox":
1490
1559
  sb_config.headless2 = False # Only for Chromium browsers
@@ -1493,6 +1562,7 @@ def pytest_configure(config):
1493
1562
  sb_config.headless2 = False # Only for Chromium browsers
1494
1563
  sb_config.headed = config.getoption("headed")
1495
1564
  sb_config.xvfb = config.getoption("xvfb")
1565
+ sb_config.xvfb_metrics = config.getoption("xvfb_metrics")
1496
1566
  sb_config.locale_code = config.getoption("locale_code")
1497
1567
  sb_config.interval = config.getoption("interval")
1498
1568
  sb_config.start_page = config.getoption("start_page")
@@ -1535,6 +1605,8 @@ def pytest_configure(config):
1535
1605
  settings.ARCHIVE_EXISTING_DOWNLOADS = True
1536
1606
  if config.getoption("skip_js_waits"):
1537
1607
  settings.SKIP_JS_WAITS = True
1608
+ if config.getoption("wait_for_angularjs"):
1609
+ settings.WAIT_FOR_ANGULARJS = True
1538
1610
  sb_config.all_scripts = config.getoption("all_scripts")
1539
1611
  sb_config._time_limit = config.getoption("time_limit")
1540
1612
  sb_config.time_limit = config.getoption("time_limit")
@@ -1549,6 +1621,7 @@ def pytest_configure(config):
1549
1621
  sb_config.block_images = config.getoption("block_images")
1550
1622
  sb_config.do_not_track = config.getoption("do_not_track")
1551
1623
  sb_config.verify_delay = config.getoption("verify_delay")
1624
+ sb_config.esc_end = config.getoption("esc_end")
1552
1625
  sb_config.recorder_mode = config.getoption("recorder_mode")
1553
1626
  sb_config.recorder_ext = config.getoption("recorder_mode") # Again
1554
1627
  sb_config.rec_behave = config.getoption("rec_behave")
@@ -1563,6 +1636,7 @@ def pytest_configure(config):
1563
1636
  elif sb_config.record_sleep and not sb_config.recorder_mode:
1564
1637
  sb_config.recorder_mode = True
1565
1638
  sb_config.recorder_ext = True
1639
+ sb_config.disable_cookies = config.getoption("disable_cookies")
1566
1640
  sb_config.disable_js = config.getoption("disable_js")
1567
1641
  sb_config.disable_csp = config.getoption("disable_csp")
1568
1642
  sb_config.disable_ws = config.getoption("disable_ws")
@@ -1598,6 +1672,7 @@ def pytest_configure(config):
1598
1672
  sb_config.shared_driver = None # The default driver for session reuse
1599
1673
  sb_config.crumbs = config.getoption("crumbs")
1600
1674
  sb_config._disable_beforeunload = config.getoption("_disable_beforeunload")
1675
+ sb_config.window_position = config.getoption("window_position")
1601
1676
  sb_config.window_size = config.getoption("window_size")
1602
1677
  sb_config.maximize_option = config.getoption("maximize_option")
1603
1678
  sb_config.save_screenshot = config.getoption("save_screenshot")
@@ -1634,6 +1709,7 @@ def pytest_configure(config):
1634
1709
  sb_config._saved_dashboard_pie = None # Copy of pie chart for html report
1635
1710
  sb_config._dash_final_summary = None # Dash status to add to html report
1636
1711
  sb_config._html_report_name = None # The name of the pytest html report
1712
+ sb_config._html_report_copy = None # The copy of the pytest html report
1637
1713
 
1638
1714
  arg_join = " ".join(sys_argv)
1639
1715
  if (
@@ -1667,6 +1743,7 @@ def pytest_configure(config):
1667
1743
  if sb_config.dashboard:
1668
1744
  if sb_config._html_report_name == "dashboard.html":
1669
1745
  sb_config._dash_is_html_report = True
1746
+ sb_config._html_report_copy = "last_report.html"
1670
1747
 
1671
1748
  # Recorder Mode does not support multi-threaded / multi-process runs.
1672
1749
  if sb_config.recorder_mode and sb_config._multithreaded:
@@ -1691,18 +1768,22 @@ def pytest_configure(config):
1691
1768
  and not sb_config.headless2
1692
1769
  and not sb_config.xvfb
1693
1770
  ):
1694
- print(
1695
- "(Linux uses --headless by default. "
1696
- "To override, use --headed / --gui. "
1697
- "For Xvfb mode instead, use --xvfb. "
1698
- "Or you can hide this info by using "
1699
- "--headless / --headless2.)"
1700
- )
1701
- sb_config.headless = True
1771
+ if not sb_config.undetectable:
1772
+ print(
1773
+ "(Linux uses --headless by default. "
1774
+ "To override, use --headed / --gui. "
1775
+ "For Xvfb mode instead, use --xvfb. "
1776
+ "Or you can hide this info by using "
1777
+ "--headless / --headless2 / --uc.)"
1778
+ )
1779
+ sb_config.headless = True
1780
+ else:
1781
+ sb_config.xvfb = True
1702
1782
 
1703
1783
  # Recorder Mode can still optimize scripts in --headless2 mode.
1704
1784
  if sb_config.recorder_mode and sb_config.headless:
1705
1785
  sb_config.headless = False
1786
+ sb_config.headless1 = False
1706
1787
  sb_config.headless2 = True
1707
1788
 
1708
1789
  if not sb_config.headless and not sb_config.headless2:
@@ -1723,6 +1804,7 @@ def pytest_configure(config):
1723
1804
 
1724
1805
  if sb_config.browser == "safari" and sb_config.headless:
1725
1806
  sb_config.headless = False # Safari doesn't support headless mode
1807
+ sb_config.headless1 = False
1726
1808
 
1727
1809
  if sb_config.dash_title:
1728
1810
  constants.Dashboard.TITLE = sb_config.dash_title.replace("_", " ")
@@ -1793,10 +1875,8 @@ def _create_dashboard_assets_():
1793
1875
  abs_path = os.path.abspath(".")
1794
1876
  assets_folder = os.path.join(abs_path, "assets")
1795
1877
  if not os.path.exists(assets_folder):
1796
- try:
1878
+ with suppress(Exception):
1797
1879
  os.makedirs(assets_folder, exist_ok=True)
1798
- except Exception:
1799
- pass
1800
1880
  pytest_style_css = os.path.join(assets_folder, "pytest_style.css")
1801
1881
  add_pytest_style_css = True
1802
1882
  if os.path.exists(pytest_style_css):
@@ -1868,20 +1948,14 @@ def pytest_collection_finish(session):
1868
1948
  dash_path = os.path.join(os.getcwd(), "dashboard.html")
1869
1949
  dash_url = "file://" + dash_path.replace("\\", "/")
1870
1950
  star_len = len("Dashboard: ") + len(dash_url)
1871
- try:
1951
+ with suppress(Exception):
1872
1952
  terminal_size = os.get_terminal_size().columns
1873
1953
  if terminal_size > 30 and star_len > terminal_size:
1874
1954
  star_len = terminal_size
1875
- except Exception:
1876
- pass
1877
1955
  stars = "*" * star_len
1878
1956
  c1 = ""
1879
1957
  cr = ""
1880
1958
  if "linux" not in sys.platform:
1881
- if is_windows and hasattr(colorama, "just_fix_windows_console"):
1882
- colorama.just_fix_windows_console()
1883
- else:
1884
- colorama.init(autoreset=True)
1885
1959
  c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX
1886
1960
  cr = colorama.Style.RESET_ALL
1887
1961
  if sb_config._multithreaded:
@@ -1915,11 +1989,11 @@ def pytest_runtest_teardown(item):
1915
1989
  (Has zero effect on tests using --reuse-session / --rs)"""
1916
1990
  if "--co" in sys_argv or "--collect-only" in sys_argv:
1917
1991
  return
1918
- try:
1992
+ with suppress(Exception):
1919
1993
  if hasattr(item, "_testcase") or hasattr(sb_config, "_sb_pdb_driver"):
1920
1994
  if hasattr(item, "_testcase"):
1921
1995
  self = item._testcase
1922
- try:
1996
+ with suppress(Exception):
1923
1997
  if (
1924
1998
  hasattr(self, "driver")
1925
1999
  and self.driver
@@ -1927,22 +2001,18 @@ def pytest_runtest_teardown(item):
1927
2001
  ):
1928
2002
  if not (is_windows or self.driver.service.process):
1929
2003
  self.driver.quit()
1930
- except Exception:
1931
- pass
1932
2004
  elif (
1933
2005
  hasattr(sb_config, "_sb_pdb_driver")
1934
2006
  and sb_config._sb_pdb_driver
1935
2007
  ):
1936
- try:
2008
+ with suppress(Exception):
1937
2009
  if (
1938
2010
  not is_windows
1939
2011
  or sb_config._sb_pdb_driver.service.process
1940
2012
  ):
1941
2013
  sb_config._sb_pdb_driver.quit()
1942
2014
  sb_config._sb_pdb_driver = None
1943
- except Exception:
1944
- pass
1945
- try:
2015
+ with suppress(Exception):
1946
2016
  if (
1947
2017
  hasattr(self, "_xvfb_display")
1948
2018
  and self._xvfb_display
@@ -1959,10 +2029,6 @@ def pytest_runtest_teardown(item):
1959
2029
  ):
1960
2030
  sb_config._virtual_display.stop()
1961
2031
  sb_config._virtual_display = None
1962
- except Exception:
1963
- pass
1964
- except Exception:
1965
- pass
1966
2032
  if (
1967
2033
  (
1968
2034
  sb_config._has_exception
@@ -1975,6 +2041,7 @@ def pytest_runtest_teardown(item):
1975
2041
  if (
1976
2042
  "-s" in sys_argv
1977
2043
  or "--capture=no" in sys_argv
2044
+ or "--capture=tee-sys" in sys_argv
1978
2045
  or (
1979
2046
  hasattr(sb_config.pytest_config, "invocation_params")
1980
2047
  and (
@@ -1982,6 +2049,9 @@ def pytest_runtest_teardown(item):
1982
2049
  or "--capture=no" in (
1983
2050
  sb_config.pytest_config.invocation_params.args
1984
2051
  )
2052
+ or "--capture=tee-sys" in (
2053
+ sb_config.pytest_config.invocation_params.args
2054
+ )
1985
2055
  )
1986
2056
  )
1987
2057
  ):
@@ -1990,6 +2060,10 @@ def pytest_runtest_teardown(item):
1990
2060
  sys.stdout.write("\n=> Fail Page: %s\n" % sb_config._fail_page)
1991
2061
 
1992
2062
 
2063
+ def pytest_html_duration_format(duration):
2064
+ return "%.2f" % duration
2065
+
2066
+
1993
2067
  def pytest_sessionfinish(session):
1994
2068
  pass
1995
2069
 
@@ -2040,9 +2114,11 @@ def pytest_terminal_summary(terminalreporter):
2040
2114
  )
2041
2115
 
2042
2116
 
2043
- def _perform_pytest_unconfigure_():
2117
+ def _perform_pytest_unconfigure_(config):
2044
2118
  from seleniumbase.core import proxy_helper
2045
2119
 
2120
+ reporter = config.pluginmanager.get_plugin("terminalreporter")
2121
+ duration = time.time() - reporter._sessionstarttime
2046
2122
  if (
2047
2123
  (hasattr(sb_config, "multi_proxy") and not sb_config.multi_proxy)
2048
2124
  or not hasattr(sb_config, "multi_proxy")
@@ -2067,9 +2143,95 @@ def _perform_pytest_unconfigure_():
2067
2143
  log_helper.archive_logs_if_set(
2068
2144
  constants.Logs.LATEST + "/", sb_config.archive_logs
2069
2145
  )
2146
+ if os.path.exists("./assets/"): # Used by pytest-html reports
2147
+ with suppress(Exception):
2148
+ shared_utils.make_dir_files_writable("./assets/")
2070
2149
  log_helper.clear_empty_logs()
2071
2150
  # Dashboard post-processing: Disable time-based refresh and stamp complete
2072
2151
  if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
2152
+ html_report_path = None
2153
+ the_html_r = None
2154
+ abs_path = os.path.abspath(".")
2155
+ if sb_config._html_report_name:
2156
+ html_report_path = os.path.join(
2157
+ abs_path, sb_config._html_report_name
2158
+ )
2159
+ if sb_config._html_report_copy:
2160
+ html_report_path_copy = os.path.join(
2161
+ abs_path, sb_config._html_report_copy
2162
+ )
2163
+ if (
2164
+ sb_config._using_html_report
2165
+ and html_report_path
2166
+ and os.path.exists(html_report_path)
2167
+ ):
2168
+ with open(html_report_path, "r", encoding="utf-8") as f:
2169
+ the_html_r = f.read()
2170
+ assets_chunk = "if (assets.length === 1) {"
2171
+ remove_media = "container.classList.remove('media-container')"
2172
+ rm_n_left = '<div class="media-container__nav--left"><</div>'
2173
+ rm_n_right = '<div class="media-container__nav--right">></div>'
2174
+ the_html_r = the_html_r.replace(
2175
+ assets_chunk,
2176
+ "%s %s" % (assets_chunk, remove_media),
2177
+ )
2178
+ the_html_r = the_html_r.replace(rm_n_left, "")
2179
+ the_html_r = the_html_r.replace(rm_n_right, "")
2180
+ the_html_r = the_html_r.replace("<ul>$", "$")
2181
+ the_html_r = the_html_r.replace("}<ul>", "}")
2182
+ the_html_r = the_html_r.replace(
2183
+ "<li>${val}</li>", "${val},&nbsp;&nbsp;"
2184
+ )
2185
+ the_html_r = the_html_r.replace(
2186
+ "<div>${value}</div>", "<span>${value}</span"
2187
+ )
2188
+ ph_link = '<a href="https://pypi.python.org/pypi/pytest-html">'
2189
+ sb_link = (
2190
+ '<a href="https://github.com/seleniumbase/SeleniumBase">'
2191
+ 'SeleniumBase</a>'
2192
+ )
2193
+ the_html_r = the_html_r.replace(
2194
+ ph_link, "%s and %s" % (sb_link, ph_link)
2195
+ )
2196
+ the_html_r = the_html_r.replace(
2197
+ "mediaName.innerText", "//mediaName.innerText"
2198
+ )
2199
+ the_html_r = the_html_r.replace(
2200
+ "counter.innerText", "//counter.innerText"
2201
+ )
2202
+ run_count = '<p class="run-count">'
2203
+ run_c_loc = the_html_r.find(run_count)
2204
+ rc_loc = the_html_r.find(" took ", run_c_loc)
2205
+ end_rc_loc = the_html_r.find(".</p>", rc_loc)
2206
+ run_time = "%.2f" % duration
2207
+ new_time = " ran in %s seconds" % run_time
2208
+ the_html_r = (
2209
+ the_html_r[:rc_loc] + new_time + the_html_r[end_rc_loc:]
2210
+ )
2211
+ with open(html_report_path, "w", encoding="utf-8") as f:
2212
+ f.write(the_html_r) # Finalize the HTML report
2213
+ with suppress(Exception):
2214
+ shared_utils.make_writable(html_report_path)
2215
+ with open(html_report_path_copy, "w", encoding="utf-8") as f:
2216
+ f.write(the_html_r) # Finalize the HTML report copy
2217
+ with suppress(Exception):
2218
+ shared_utils.make_writable(html_report_path_copy)
2219
+ assets_style = "./assets/style.css"
2220
+ if os.path.exists(assets_style):
2221
+ html_style = None
2222
+ with open(assets_style, "r", encoding="utf-8") as f:
2223
+ html_style = f.read()
2224
+ if html_style:
2225
+ html_style = html_style.replace("top: -50px;", "top: 2px;")
2226
+ html_style = html_style.replace("+ 50px)", "+ 40px)")
2227
+ html_style = html_style.replace("ht: 240px;", "ht: 228px;")
2228
+ html_style = html_style.replace(
2229
+ "- 80px);", "- 80px);\n margin-bottom: -42px;"
2230
+ )
2231
+ with open(assets_style, "w", encoding="utf-8") as f:
2232
+ f.write(html_style)
2233
+ with suppress(Exception):
2234
+ shared_utils.make_writable(assets_style)
2073
2235
  # Done with "pytest_unconfigure" unless using the Dashboard
2074
2236
  return
2075
2237
  stamp = ""
@@ -2151,12 +2313,34 @@ def _perform_pytest_unconfigure_():
2151
2313
  )
2152
2314
  with open(dashboard_path, "w", encoding="utf-8") as f:
2153
2315
  f.write(the_html_d) # Finalize the dashboard
2316
+ with suppress(Exception):
2317
+ shared_utils.make_writable(dashboard_path)
2318
+ assets_style = "./assets/style.css"
2319
+ if os.path.exists(assets_style):
2320
+ html_style = None
2321
+ with open(assets_style, "r", encoding="utf-8") as f:
2322
+ html_style = f.read()
2323
+ if html_style:
2324
+ html_style = html_style.replace("top: -50px;", "top: 2px;")
2325
+ html_style = html_style.replace("+ 50px)", "+ 40px)")
2326
+ html_style = html_style.replace("ht: 240px;", "ht: 228px;")
2327
+ html_style = html_style.replace(
2328
+ "- 80px);", "- 80px);\n margin-bottom: -42px;"
2329
+ )
2330
+ with open(assets_style, "w", encoding="utf-8") as f:
2331
+ f.write(html_style)
2332
+ with suppress(Exception):
2333
+ shared_utils.make_writable(assets_style)
2154
2334
  # Part 2: Appending a pytest html report with dashboard data
2155
2335
  html_report_path = None
2156
2336
  if sb_config._html_report_name:
2157
2337
  html_report_path = os.path.join(
2158
2338
  abs_path, sb_config._html_report_name
2159
2339
  )
2340
+ if sb_config._html_report_copy:
2341
+ html_report_path_copy = os.path.join(
2342
+ abs_path, sb_config._html_report_copy
2343
+ )
2160
2344
  if (
2161
2345
  sb_config._using_html_report
2162
2346
  and html_report_path
@@ -2174,7 +2358,7 @@ def _perform_pytest_unconfigure_():
2174
2358
  elif "\\" in h_r_name and h_r_name.endswith(".html"):
2175
2359
  h_r_name = h_r_name.split("\\")[-1]
2176
2360
  the_html_r = the_html_r.replace(
2177
- "<h1>%s</h1>" % h_r_name,
2361
+ '<h1 id="title">%s</h1>' % h_r_name,
2178
2362
  sb_config._saved_dashboard_pie,
2179
2363
  )
2180
2364
  the_html_r = the_html_r.replace(
@@ -2184,8 +2368,55 @@ def _perform_pytest_unconfigure_():
2184
2368
  )
2185
2369
  if sb_config._dash_final_summary:
2186
2370
  the_html_r += sb_config._dash_final_summary
2371
+ assets_chunk = "if (assets.length === 1) {"
2372
+ remove_media = "container.classList.remove('media-container')"
2373
+ rm_n_left = '<div class="media-container__nav--left"><</div>'
2374
+ rm_n_right = '<div class="media-container__nav--right">></div>'
2375
+ the_html_r = the_html_r.replace(
2376
+ assets_chunk,
2377
+ "%s %s" % (assets_chunk, remove_media),
2378
+ )
2379
+ the_html_r = the_html_r.replace(rm_n_left, "")
2380
+ the_html_r = the_html_r.replace(rm_n_right, "")
2381
+ the_html_r = the_html_r.replace("<ul>$", "$")
2382
+ the_html_r = the_html_r.replace("}<ul>", "}")
2383
+ the_html_r = the_html_r.replace(
2384
+ "<li>${val}</li>", "${val},&nbsp;&nbsp;"
2385
+ )
2386
+ the_html_r = the_html_r.replace(
2387
+ "<div>${value}</div>", "<span>${value}</span"
2388
+ )
2389
+ ph_link = '<a href="https://pypi.python.org/pypi/pytest-html">'
2390
+ sb_link = (
2391
+ '<a href="https://github.com/seleniumbase/SeleniumBase">'
2392
+ 'SeleniumBase</a>'
2393
+ )
2394
+ the_html_r = the_html_r.replace(
2395
+ ph_link, "%s and %s" % (sb_link, ph_link)
2396
+ )
2397
+ the_html_r = the_html_r.replace(
2398
+ "mediaName.innerText", "//mediaName.innerText"
2399
+ )
2400
+ the_html_r = the_html_r.replace(
2401
+ "counter.innerText", "//counter.innerText"
2402
+ )
2403
+ run_count = '<p class="run-count">'
2404
+ run_c_loc = the_html_r.find(run_count)
2405
+ rc_loc = the_html_r.find(" took ", run_c_loc)
2406
+ end_rc_loc = the_html_r.find(".</p>", rc_loc)
2407
+ run_time = "%.2f" % duration
2408
+ new_time = " ran in %s seconds" % run_time
2409
+ the_html_r = (
2410
+ the_html_r[:rc_loc] + new_time + the_html_r[end_rc_loc:]
2411
+ )
2187
2412
  with open(html_report_path, "w", encoding="utf-8") as f:
2188
2413
  f.write(the_html_r) # Finalize the HTML report
2414
+ with suppress(Exception):
2415
+ shared_utils.make_writable(html_report_path)
2416
+ with open(html_report_path_copy, "w", encoding="utf-8") as f:
2417
+ f.write(the_html_r) # Finalize the HTML report copy
2418
+ with suppress(Exception):
2419
+ shared_utils.make_writable(html_report_path_copy)
2189
2420
  except KeyboardInterrupt:
2190
2421
  pass
2191
2422
  except Exception:
@@ -2218,19 +2449,19 @@ def pytest_unconfigure(config):
2218
2449
  with open(dashboard_path, "w", encoding="utf-8") as f:
2219
2450
  f.write(sb_config._dash_html)
2220
2451
  # Dashboard Multithreaded
2221
- _perform_pytest_unconfigure_()
2452
+ _perform_pytest_unconfigure_(config)
2222
2453
  return
2223
2454
  else:
2224
2455
  # Dash Lock is missing
2225
- _perform_pytest_unconfigure_()
2456
+ _perform_pytest_unconfigure_(config)
2226
2457
  return
2227
2458
  with dash_lock:
2228
2459
  # Multi-threaded tests
2229
- _perform_pytest_unconfigure_()
2460
+ _perform_pytest_unconfigure_(config)
2230
2461
  return
2231
2462
  else:
2232
2463
  # Single-threaded tests
2233
- _perform_pytest_unconfigure_()
2464
+ _perform_pytest_unconfigure_(config)
2234
2465
  return
2235
2466
 
2236
2467
 
@@ -2343,7 +2574,7 @@ def pytest_runtest_makereport(item, call):
2343
2574
  )
2344
2575
  if log_path:
2345
2576
  sb_config._log_fail_data()
2346
- try:
2577
+ with suppress(Exception):
2347
2578
  extra_report = None
2348
2579
  if hasattr(item, "_testcase"):
2349
2580
  extra_report = item._testcase._html_report_extra
@@ -2379,7 +2610,7 @@ def pytest_runtest_makereport(item, call):
2379
2610
  return
2380
2611
  extra = getattr(report, "extra", [])
2381
2612
  if len(extra_report) > 1 and extra_report[1]["content"]:
2382
- report.extra = extra + extra_report
2613
+ report.extras = extra + extra_report
2383
2614
  if sb_config._dash_is_html_report:
2384
2615
  # If the Dashboard URL is the same as the HTML Report URL,
2385
2616
  # have the html report refresh back to a dashboard on update.
@@ -2387,6 +2618,4 @@ def pytest_runtest_makereport(item, call):
2387
2618
  '<script type="text/javascript" src="%s">'
2388
2619
  "</script>" % constants.Dashboard.LIVE_JS
2389
2620
  )
2390
- report.extra.append(pytest_html.extras.html(refresh_updates))
2391
- except Exception:
2392
- pass
2621
+ report.extras.append(pytest_html.extras.html(refresh_updates))