seleniumbase 4.24.10__py3-none-any.whl → 4.33.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) 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/detect_b_ver.py +7 -8
  25. seleniumbase/core/log_helper.py +42 -27
  26. seleniumbase/core/mysql.py +1 -4
  27. seleniumbase/core/proxy_helper.py +35 -30
  28. seleniumbase/core/recorder_helper.py +24 -5
  29. seleniumbase/core/sb_cdp.py +1951 -0
  30. seleniumbase/core/sb_driver.py +162 -8
  31. seleniumbase/core/settings_parser.py +6 -0
  32. seleniumbase/core/style_sheet.py +10 -0
  33. seleniumbase/extensions/recorder.zip +0 -0
  34. seleniumbase/fixtures/base_case.py +1234 -632
  35. seleniumbase/fixtures/constants.py +10 -1
  36. seleniumbase/fixtures/js_utils.py +171 -144
  37. seleniumbase/fixtures/page_actions.py +177 -13
  38. seleniumbase/fixtures/page_utils.py +25 -53
  39. seleniumbase/fixtures/shared_utils.py +97 -11
  40. seleniumbase/js_code/active_css_js.py +1 -1
  41. seleniumbase/js_code/recorder_js.py +1 -1
  42. seleniumbase/plugins/base_plugin.py +2 -3
  43. seleniumbase/plugins/driver_manager.py +340 -65
  44. seleniumbase/plugins/pytest_plugin.py +276 -47
  45. seleniumbase/plugins/sb_manager.py +412 -99
  46. seleniumbase/plugins/selenium_plugin.py +122 -17
  47. seleniumbase/translate/translator.py +0 -7
  48. seleniumbase/undetected/__init__.py +59 -52
  49. seleniumbase/undetected/cdp.py +0 -1
  50. seleniumbase/undetected/cdp_driver/__init__.py +1 -0
  51. seleniumbase/undetected/cdp_driver/_contradict.py +110 -0
  52. seleniumbase/undetected/cdp_driver/browser.py +829 -0
  53. seleniumbase/undetected/cdp_driver/cdp_util.py +458 -0
  54. seleniumbase/undetected/cdp_driver/config.py +334 -0
  55. seleniumbase/undetected/cdp_driver/connection.py +639 -0
  56. seleniumbase/undetected/cdp_driver/element.py +1168 -0
  57. seleniumbase/undetected/cdp_driver/tab.py +1323 -0
  58. seleniumbase/undetected/dprocess.py +4 -7
  59. seleniumbase/undetected/options.py +6 -8
  60. seleniumbase/undetected/patcher.py +11 -13
  61. seleniumbase/undetected/reactor.py +0 -1
  62. seleniumbase/undetected/webelement.py +16 -3
  63. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/LICENSE +1 -1
  64. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/METADATA +299 -252
  65. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/RECORD +68 -70
  66. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/WHEEL +1 -1
  67. sbase/ReadMe.txt +0 -2
  68. seleniumbase/ReadMe.md +0 -25
  69. seleniumbase/common/ReadMe.md +0 -71
  70. seleniumbase/console_scripts/ReadMe.md +0 -731
  71. seleniumbase/drivers/ReadMe.md +0 -27
  72. seleniumbase/extensions/ReadMe.md +0 -12
  73. seleniumbase/masterqa/ReadMe.md +0 -61
  74. seleniumbase/resources/ReadMe.md +0 -31
  75. seleniumbase/resources/favicon.ico +0 -0
  76. seleniumbase/utilities/selenium_grid/ReadMe.md +0 -84
  77. seleniumbase/utilities/selenium_ide/ReadMe.md +0 -111
  78. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/entry_points.txt +0 -0
  79. {seleniumbase-4.24.10.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))