seleniumbase 4.33.4__py3-none-any.whl → 4.34.2__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.
- seleniumbase/__version__.py +1 -1
- seleniumbase/behave/behave_sb.py +10 -2
- seleniumbase/console_scripts/run.py +6 -2
- seleniumbase/console_scripts/sb_commander.py +5 -5
- seleniumbase/console_scripts/sb_install.py +235 -6
- seleniumbase/console_scripts/sb_mkdir.py +1 -0
- seleniumbase/core/browser_launcher.py +358 -105
- seleniumbase/core/log_helper.py +33 -12
- seleniumbase/core/proxy_helper.py +35 -30
- seleniumbase/core/sb_cdp.py +277 -74
- seleniumbase/core/settings_parser.py +2 -0
- seleniumbase/core/style_sheet.py +10 -0
- seleniumbase/fixtures/base_case.py +216 -127
- seleniumbase/fixtures/constants.py +3 -0
- seleniumbase/fixtures/js_utils.py +2 -0
- seleniumbase/fixtures/page_actions.py +7 -2
- seleniumbase/fixtures/shared_utils.py +25 -0
- seleniumbase/plugins/driver_manager.py +28 -0
- seleniumbase/plugins/pytest_plugin.py +110 -0
- seleniumbase/plugins/sb_manager.py +41 -0
- seleniumbase/plugins/selenium_plugin.py +9 -0
- seleniumbase/undetected/cdp_driver/_contradict.py +3 -3
- seleniumbase/undetected/cdp_driver/browser.py +8 -6
- seleniumbase/undetected/cdp_driver/cdp_util.py +3 -0
- seleniumbase/undetected/cdp_driver/config.py +0 -1
- seleniumbase/undetected/cdp_driver/element.py +22 -20
- seleniumbase/undetected/patcher.py +20 -5
- {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/LICENSE +1 -1
- {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/METADATA +111 -86
- {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/RECORD +33 -33
- {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/WHEEL +1 -1
- {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/entry_points.txt +0 -0
- {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/top_level.txt +0 -0
| @@ -400,6 +400,7 @@ class ValidBinaries: | |
| 400 400 | 
             
                    "google-chrome-beta",
         | 
| 401 401 | 
             
                    "google-chrome-dev",
         | 
| 402 402 | 
             
                    "google-chrome-unstable",
         | 
| 403 | 
            +
                    "chrome-headless-shell",
         | 
| 403 404 | 
             
                    "brave-browser",
         | 
| 404 405 | 
             
                    "brave-browser-stable",
         | 
| 405 406 | 
             
                    "brave",
         | 
| @@ -418,6 +419,7 @@ class ValidBinaries: | |
| 418 419 | 
             
                    "Google Chrome",
         | 
| 419 420 | 
             
                    "Chromium",
         | 
| 420 421 | 
             
                    "Google Chrome for Testing",
         | 
| 422 | 
            +
                    "chrome-headless-shell",
         | 
| 421 423 | 
             
                    "Google Chrome Beta",
         | 
| 422 424 | 
             
                    "Google Chrome Dev",
         | 
| 423 425 | 
             
                    "Brave Browser",
         | 
| @@ -429,6 +431,7 @@ class ValidBinaries: | |
| 429 431 | 
             
                valid_chrome_binaries_on_windows = [
         | 
| 430 432 | 
             
                    "chrome.exe",
         | 
| 431 433 | 
             
                    "chromium.exe",
         | 
| 434 | 
            +
                    "chrome-headless-shell.exe",
         | 
| 432 435 | 
             
                    "brave.exe",
         | 
| 433 436 | 
             
                    "opera.exe",
         | 
| 434 437 | 
             
                ]
         | 
| @@ -1188,6 +1188,8 @@ def highlight_with_jquery_2(driver, message, selector, o_bs, msg_dur): | |
| 1188 1188 | 
             
            def get_active_element_css(driver):
         | 
| 1189 1189 | 
             
                from seleniumbase.js_code import active_css_js
         | 
| 1190 1190 |  | 
| 1191 | 
            +
                if shared_utils.is_cdp_swap_needed(driver):
         | 
| 1192 | 
            +
                    return driver.cdp.get_active_element_css()
         | 
| 1191 1193 | 
             
                return execute_script(driver, active_css_js.get_active_element_css)
         | 
| 1192 1194 |  | 
| 1193 1195 |  | 
| @@ -1342,7 +1342,8 @@ def save_page_source(driver, name, folder=None): | |
| 1342 1342 | 
             
                """
         | 
| 1343 1343 | 
             
                from seleniumbase.core import log_helper
         | 
| 1344 1344 |  | 
| 1345 | 
            -
                 | 
| 1345 | 
            +
                if not __is_cdp_swap_needed(driver):
         | 
| 1346 | 
            +
                    _reconnect_if_disconnected(driver)  # If disconnected without CDP
         | 
| 1346 1347 | 
             
                if not name.endswith(".html"):
         | 
| 1347 1348 | 
             
                    name = name + ".html"
         | 
| 1348 1349 | 
             
                if folder:
         | 
| @@ -1353,7 +1354,11 @@ def save_page_source(driver, name, folder=None): | |
| 1353 1354 | 
             
                    html_file_path = os.path.join(file_path, name)
         | 
| 1354 1355 | 
             
                else:
         | 
| 1355 1356 | 
             
                    html_file_path = name
         | 
| 1356 | 
            -
                page_source =  | 
| 1357 | 
            +
                page_source = None
         | 
| 1358 | 
            +
                if __is_cdp_swap_needed(driver):
         | 
| 1359 | 
            +
                    page_source = driver.cdp.get_page_source()
         | 
| 1360 | 
            +
                else:
         | 
| 1361 | 
            +
                    page_source = driver.page_source
         | 
| 1357 1362 | 
             
                html_file = codecs.open(html_file_path, "w+", "utf-8")
         | 
| 1358 1363 | 
             
                rendered_source = log_helper.get_html_source_with_base_href(
         | 
| 1359 1364 | 
             
                    driver, page_source
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            """Shared utility methods"""
         | 
| 2 2 | 
             
            import colorama
         | 
| 3 3 | 
             
            import os
         | 
| 4 | 
            +
            import pathlib
         | 
| 4 5 | 
             
            import platform
         | 
| 5 6 | 
             
            import sys
         | 
| 6 7 | 
             
            import time
         | 
| @@ -128,6 +129,30 @@ def is_chrome_130_or_newer(self, binary_location=None): | |
| 128 129 | 
             
                return False
         | 
| 129 130 |  | 
| 130 131 |  | 
| 132 | 
            +
            def make_dir_files_writable(dir_path):
         | 
| 133 | 
            +
                # Make all files in the given directory writable.
         | 
| 134 | 
            +
                for file_path in pathlib.Path(dir_path).glob("*"):
         | 
| 135 | 
            +
                    if file_path.is_file():
         | 
| 136 | 
            +
                        mode = os.stat(file_path).st_mode
         | 
| 137 | 
            +
                        mode |= (mode & 0o444) >> 1  # copy R bits to W
         | 
| 138 | 
            +
                        with suppress(Exception):
         | 
| 139 | 
            +
                            os.chmod(file_path, mode)
         | 
| 140 | 
            +
             | 
| 141 | 
            +
             | 
| 142 | 
            +
            def make_writable(file_path):
         | 
| 143 | 
            +
                # Set permissions to: "If you can read it, you can write it."
         | 
| 144 | 
            +
                mode = os.stat(file_path).st_mode
         | 
| 145 | 
            +
                mode |= (mode & 0o444) >> 1  # copy R bits to W
         | 
| 146 | 
            +
                os.chmod(file_path, mode)
         | 
| 147 | 
            +
             | 
| 148 | 
            +
             | 
| 149 | 
            +
            def make_executable(file_path):
         | 
| 150 | 
            +
                # Set permissions to: "If you can read it, you can execute it."
         | 
| 151 | 
            +
                mode = os.stat(file_path).st_mode
         | 
| 152 | 
            +
                mode |= (mode & 0o444) >> 2  # copy R bits to X
         | 
| 153 | 
            +
                os.chmod(file_path, mode)
         | 
| 154 | 
            +
             | 
| 155 | 
            +
             | 
| 131 156 | 
             
            def format_exc(exception, message):
         | 
| 132 157 | 
             
                """Formats an exception message to make the output cleaner."""
         | 
| 133 158 | 
             
                from selenium.common.exceptions import ElementNotVisibleException
         | 
| @@ -530,6 +530,34 @@ def Driver( | |
| 530 530 | 
             
                            break
         | 
| 531 531 | 
             
                        count += 1
         | 
| 532 532 | 
             
                user_agent = agent
         | 
| 533 | 
            +
                found_bl = None
         | 
| 534 | 
            +
                if binary_location is None and "--binary-location" in arg_join:
         | 
| 535 | 
            +
                    count = 0
         | 
| 536 | 
            +
                    for arg in sys_argv:
         | 
| 537 | 
            +
                        if arg.startswith("--binary-location="):
         | 
| 538 | 
            +
                            found_bl = arg.split("--binary-location=")[1]
         | 
| 539 | 
            +
                            break
         | 
| 540 | 
            +
                        elif arg == "--binary-location" and len(sys_argv) > count + 1:
         | 
| 541 | 
            +
                            found_bl = sys_argv[count + 1]
         | 
| 542 | 
            +
                            if found_bl.startswith("-"):
         | 
| 543 | 
            +
                                found_bl = None
         | 
| 544 | 
            +
                            break
         | 
| 545 | 
            +
                        count += 1
         | 
| 546 | 
            +
                    if found_bl:
         | 
| 547 | 
            +
                        binary_location = found_bl
         | 
| 548 | 
            +
                if binary_location is None and "--bl=" in arg_join:
         | 
| 549 | 
            +
                    for arg in sys_argv:
         | 
| 550 | 
            +
                        if arg.startswith("--bl="):
         | 
| 551 | 
            +
                            binary_location = arg.split("--bl=")[1]
         | 
| 552 | 
            +
                            break
         | 
| 553 | 
            +
                if (
         | 
| 554 | 
            +
                    binary_location
         | 
| 555 | 
            +
                    and binary_location.lower() == "chs"
         | 
| 556 | 
            +
                    and browser == "chrome"
         | 
| 557 | 
            +
                ):
         | 
| 558 | 
            +
                    headless = True
         | 
| 559 | 
            +
                    headless1 = False
         | 
| 560 | 
            +
                    headless2 = False
         | 
| 533 561 | 
             
                recorder_mode = False
         | 
| 534 562 | 
             
                if recorder_ext:
         | 
| 535 563 | 
             
                    recorder_mode = True
         | 
| @@ -656,6 +656,7 @@ def pytest_addoption(parser): | |
| 656 656 | 
             
                parser.addoption(
         | 
| 657 657 | 
             
                    "--binary_location",
         | 
| 658 658 | 
             
                    "--binary-location",
         | 
| 659 | 
            +
                    "--bl",
         | 
| 659 660 | 
             
                    action="store",
         | 
| 660 661 | 
             
                    dest="binary_location",
         | 
| 661 662 | 
             
                    default=None,
         | 
| @@ -1371,6 +1372,7 @@ def pytest_addoption(parser): | |
| 1371 1372 |  | 
| 1372 1373 | 
             
                arg_join = " ".join(sys_argv)
         | 
| 1373 1374 | 
             
                sb_config._browser_shortcut = None
         | 
| 1375 | 
            +
                sb_config._vd_list = []
         | 
| 1374 1376 |  | 
| 1375 1377 | 
             
                # SeleniumBase does not support pytest-timeout due to hanging browsers.
         | 
| 1376 1378 | 
             
                for arg in sys_argv:
         | 
| @@ -1573,6 +1575,14 @@ def pytest_configure(config): | |
| 1573 1575 | 
             
                sb_config.extension_dir = config.getoption("extension_dir")
         | 
| 1574 1576 | 
             
                sb_config.disable_features = config.getoption("disable_features")
         | 
| 1575 1577 | 
             
                sb_config.binary_location = config.getoption("binary_location")
         | 
| 1578 | 
            +
                if (
         | 
| 1579 | 
            +
                    sb_config.binary_location
         | 
| 1580 | 
            +
                    and sb_config.binary_location.lower() == "chs"
         | 
| 1581 | 
            +
                    and sb_config.browser == "chrome"
         | 
| 1582 | 
            +
                ):
         | 
| 1583 | 
            +
                    sb_config.headless = True
         | 
| 1584 | 
            +
                    sb_config.headless1 = False
         | 
| 1585 | 
            +
                    sb_config.headless2 = False
         | 
| 1576 1586 | 
             
                sb_config.driver_version = config.getoption("driver_version")
         | 
| 1577 1587 | 
             
                sb_config.page_load_strategy = config.getoption("page_load_strategy")
         | 
| 1578 1588 | 
             
                sb_config.with_testing_base = config.getoption("with_testing_base")
         | 
| @@ -1709,6 +1719,7 @@ def pytest_configure(config): | |
| 1709 1719 | 
             
                sb_config._saved_dashboard_pie = None  # Copy of pie chart for html report
         | 
| 1710 1720 | 
             
                sb_config._dash_final_summary = None  # Dash status to add to html report
         | 
| 1711 1721 | 
             
                sb_config._html_report_name = None  # The name of the pytest html report
         | 
| 1722 | 
            +
                sb_config._html_report_copy = None  # The copy of the pytest html report
         | 
| 1712 1723 |  | 
| 1713 1724 | 
             
                arg_join = " ".join(sys_argv)
         | 
| 1714 1725 | 
             
                if (
         | 
| @@ -1742,6 +1753,7 @@ def pytest_configure(config): | |
| 1742 1753 | 
             
                    if sb_config.dashboard:
         | 
| 1743 1754 | 
             
                        if sb_config._html_report_name == "dashboard.html":
         | 
| 1744 1755 | 
             
                            sb_config._dash_is_html_report = True
         | 
| 1756 | 
            +
                    sb_config._html_report_copy = "last_report.html"
         | 
| 1745 1757 |  | 
| 1746 1758 | 
             
                # Recorder Mode does not support multi-threaded / multi-process runs.
         | 
| 1747 1759 | 
             
                if sb_config.recorder_mode and sb_config._multithreaded:
         | 
| @@ -2015,6 +2027,13 @@ def pytest_runtest_teardown(item): | |
| 2015 2027 | 
             
                            hasattr(self, "_xvfb_display")
         | 
| 2016 2028 | 
             
                            and self._xvfb_display
         | 
| 2017 2029 | 
             
                            and hasattr(self._xvfb_display, "stop")
         | 
| 2030 | 
            +
                            and (
         | 
| 2031 | 
            +
                                not hasattr(sb_config, "reuse_session")
         | 
| 2032 | 
            +
                                or (
         | 
| 2033 | 
            +
                                    hasattr(sb_config, "reuse_session")
         | 
| 2034 | 
            +
                                    and not sb_config.reuse_session
         | 
| 2035 | 
            +
                                )
         | 
| 2036 | 
            +
                            )
         | 
| 2018 2037 | 
             
                        ):
         | 
| 2019 2038 | 
             
                            self.headless_active = False
         | 
| 2020 2039 | 
             
                            sb_config.headless_active = False
         | 
| @@ -2024,6 +2043,13 @@ def pytest_runtest_teardown(item): | |
| 2024 2043 | 
             
                            hasattr(sb_config, "_virtual_display")
         | 
| 2025 2044 | 
             
                            and sb_config._virtual_display
         | 
| 2026 2045 | 
             
                            and hasattr(sb_config._virtual_display, "stop")
         | 
| 2046 | 
            +
                            and (
         | 
| 2047 | 
            +
                                not hasattr(sb_config, "reuse_session")
         | 
| 2048 | 
            +
                                or (
         | 
| 2049 | 
            +
                                    hasattr(sb_config, "reuse_session")
         | 
| 2050 | 
            +
                                    and not sb_config.reuse_session
         | 
| 2051 | 
            +
                                )
         | 
| 2052 | 
            +
                            )
         | 
| 2027 2053 | 
             
                        ):
         | 
| 2028 2054 | 
             
                            sb_config._virtual_display.stop()
         | 
| 2029 2055 | 
             
                            sb_config._virtual_display = None
         | 
| @@ -2137,10 +2163,28 @@ def _perform_pytest_unconfigure_(config): | |
| 2137 2163 | 
             
                        except Exception:
         | 
| 2138 2164 | 
             
                            pass
         | 
| 2139 2165 | 
             
                    sb_config.shared_driver = None
         | 
| 2166 | 
            +
                    with suppress(Exception):
         | 
| 2167 | 
            +
                        if (
         | 
| 2168 | 
            +
                            hasattr(sb_config, "_virtual_display")
         | 
| 2169 | 
            +
                            and sb_config._virtual_display
         | 
| 2170 | 
            +
                            and hasattr(sb_config._virtual_display, "stop")
         | 
| 2171 | 
            +
                        ):
         | 
| 2172 | 
            +
                            sb_config._virtual_display.stop()
         | 
| 2173 | 
            +
                            sb_config._virtual_display = None
         | 
| 2174 | 
            +
                            sb_config.headless_active = False
         | 
| 2175 | 
            +
                        if hasattr(sb_config, "_vd_list") and sb_config._vd_list:
         | 
| 2176 | 
            +
                            if isinstance(sb_config._vd_list, list):
         | 
| 2177 | 
            +
                                for display in sb_config._vd_list:
         | 
| 2178 | 
            +
                                    if display:
         | 
| 2179 | 
            +
                                        with suppress(Exception):
         | 
| 2180 | 
            +
                                            display.stop()
         | 
| 2140 2181 | 
             
                if hasattr(sb_config, "log_path") and sb_config.item_count > 0:
         | 
| 2141 2182 | 
             
                    log_helper.archive_logs_if_set(
         | 
| 2142 2183 | 
             
                        constants.Logs.LATEST + "/", sb_config.archive_logs
         | 
| 2143 2184 | 
             
                    )
         | 
| 2185 | 
            +
                    if os.path.exists("./assets/"):  # Used by pytest-html reports
         | 
| 2186 | 
            +
                        with suppress(Exception):
         | 
| 2187 | 
            +
                            shared_utils.make_dir_files_writable("./assets/")
         | 
| 2144 2188 | 
             
                log_helper.clear_empty_logs()
         | 
| 2145 2189 | 
             
                # Dashboard post-processing: Disable time-based refresh and stamp complete
         | 
| 2146 2190 | 
             
                if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
         | 
| @@ -2151,6 +2195,10 @@ def _perform_pytest_unconfigure_(config): | |
| 2151 2195 | 
             
                        html_report_path = os.path.join(
         | 
| 2152 2196 | 
             
                            abs_path, sb_config._html_report_name
         | 
| 2153 2197 | 
             
                        )
         | 
| 2198 | 
            +
                    if sb_config._html_report_copy:
         | 
| 2199 | 
            +
                        html_report_path_copy = os.path.join(
         | 
| 2200 | 
            +
                            abs_path, sb_config._html_report_copy
         | 
| 2201 | 
            +
                        )
         | 
| 2154 2202 | 
             
                    if (
         | 
| 2155 2203 | 
             
                        sb_config._using_html_report
         | 
| 2156 2204 | 
             
                        and html_report_path
         | 
| @@ -2184,6 +2232,9 @@ def _perform_pytest_unconfigure_(config): | |
| 2184 2232 | 
             
                        the_html_r = the_html_r.replace(
         | 
| 2185 2233 | 
             
                            ph_link, "%s and %s" % (sb_link, ph_link)
         | 
| 2186 2234 | 
             
                        )
         | 
| 2235 | 
            +
                        the_html_r = the_html_r.replace(
         | 
| 2236 | 
            +
                            "findAll('.collapsible", "//findAll('.collapsible"
         | 
| 2237 | 
            +
                        )
         | 
| 2187 2238 | 
             
                        the_html_r = the_html_r.replace(
         | 
| 2188 2239 | 
             
                            "mediaName.innerText", "//mediaName.innerText"
         | 
| 2189 2240 | 
             
                        )
         | 
| @@ -2201,6 +2252,31 @@ def _perform_pytest_unconfigure_(config): | |
| 2201 2252 | 
             
                        )
         | 
| 2202 2253 | 
             
                        with open(html_report_path, "w", encoding="utf-8") as f:
         | 
| 2203 2254 | 
             
                            f.write(the_html_r)  # Finalize the HTML report
         | 
| 2255 | 
            +
                        with suppress(Exception):
         | 
| 2256 | 
            +
                            shared_utils.make_writable(html_report_path)
         | 
| 2257 | 
            +
                        with open(html_report_path_copy, "w", encoding="utf-8") as f:
         | 
| 2258 | 
            +
                            f.write(the_html_r)  # Finalize the HTML report copy
         | 
| 2259 | 
            +
                        with suppress(Exception):
         | 
| 2260 | 
            +
                            shared_utils.make_writable(html_report_path_copy)
         | 
| 2261 | 
            +
                        assets_style = "./assets/style.css"
         | 
| 2262 | 
            +
                        if os.path.exists(assets_style):
         | 
| 2263 | 
            +
                            html_style = None
         | 
| 2264 | 
            +
                            with open(assets_style, "r", encoding="utf-8") as f:
         | 
| 2265 | 
            +
                                html_style = f.read()
         | 
| 2266 | 
            +
                            if html_style:
         | 
| 2267 | 
            +
                                html_style = html_style.replace("top: -50px;", "top: 2px;")
         | 
| 2268 | 
            +
                                html_style = html_style.replace("+ 50px)", "+ 40px)")
         | 
| 2269 | 
            +
                                html_style = html_style.replace("ht: 240px;", "ht: 228px;")
         | 
| 2270 | 
            +
                                html_style = html_style.replace(
         | 
| 2271 | 
            +
                                    "- 80px);", "- 80px);\n  margin-bottom: -42px;"
         | 
| 2272 | 
            +
                                )
         | 
| 2273 | 
            +
                                html_style = html_style.replace(".collapsible", ".oldc")
         | 
| 2274 | 
            +
                                html_style = html_style.replace(" (hide details)", "")
         | 
| 2275 | 
            +
                                html_style = html_style.replace(" (show details)", "")
         | 
| 2276 | 
            +
                            with open(assets_style, "w", encoding="utf-8") as f:
         | 
| 2277 | 
            +
                                f.write(html_style)
         | 
| 2278 | 
            +
                            with suppress(Exception):
         | 
| 2279 | 
            +
                                shared_utils.make_writable(assets_style)
         | 
| 2204 2280 | 
             
                    # Done with "pytest_unconfigure" unless using the Dashboard
         | 
| 2205 2281 | 
             
                    return
         | 
| 2206 2282 | 
             
                stamp = ""
         | 
| @@ -2282,12 +2358,37 @@ def _perform_pytest_unconfigure_(config): | |
| 2282 2358 | 
             
                            )
         | 
| 2283 2359 | 
             
                        with open(dashboard_path, "w", encoding="utf-8") as f:
         | 
| 2284 2360 | 
             
                            f.write(the_html_d)  # Finalize the dashboard
         | 
| 2361 | 
            +
                        with suppress(Exception):
         | 
| 2362 | 
            +
                            shared_utils.make_writable(dashboard_path)
         | 
| 2363 | 
            +
                        assets_style = "./assets/style.css"
         | 
| 2364 | 
            +
                        if os.path.exists(assets_style):
         | 
| 2365 | 
            +
                            html_style = None
         | 
| 2366 | 
            +
                            with open(assets_style, "r", encoding="utf-8") as f:
         | 
| 2367 | 
            +
                                html_style = f.read()
         | 
| 2368 | 
            +
                            if html_style:
         | 
| 2369 | 
            +
                                html_style = html_style.replace("top: -50px;", "top: 2px;")
         | 
| 2370 | 
            +
                                html_style = html_style.replace("+ 50px)", "+ 40px)")
         | 
| 2371 | 
            +
                                html_style = html_style.replace("ht: 240px;", "ht: 228px;")
         | 
| 2372 | 
            +
                                html_style = html_style.replace(
         | 
| 2373 | 
            +
                                    "- 80px);", "- 80px);\n  margin-bottom: -42px;"
         | 
| 2374 | 
            +
                                )
         | 
| 2375 | 
            +
                                html_style = html_style.replace(".collapsible", ".oldc")
         | 
| 2376 | 
            +
                                html_style = html_style.replace(" (hide details)", "")
         | 
| 2377 | 
            +
                                html_style = html_style.replace(" (show details)", "")
         | 
| 2378 | 
            +
                            with open(assets_style, "w", encoding="utf-8") as f:
         | 
| 2379 | 
            +
                                f.write(html_style)
         | 
| 2380 | 
            +
                            with suppress(Exception):
         | 
| 2381 | 
            +
                                shared_utils.make_writable(assets_style)
         | 
| 2285 2382 | 
             
                        # Part 2: Appending a pytest html report with dashboard data
         | 
| 2286 2383 | 
             
                        html_report_path = None
         | 
| 2287 2384 | 
             
                        if sb_config._html_report_name:
         | 
| 2288 2385 | 
             
                            html_report_path = os.path.join(
         | 
| 2289 2386 | 
             
                                abs_path, sb_config._html_report_name
         | 
| 2290 2387 | 
             
                            )
         | 
| 2388 | 
            +
                        if sb_config._html_report_copy:
         | 
| 2389 | 
            +
                            html_report_path_copy = os.path.join(
         | 
| 2390 | 
            +
                                abs_path, sb_config._html_report_copy
         | 
| 2391 | 
            +
                            )
         | 
| 2291 2392 | 
             
                        if (
         | 
| 2292 2393 | 
             
                            sb_config._using_html_report
         | 
| 2293 2394 | 
             
                            and html_report_path
         | 
| @@ -2341,6 +2442,9 @@ def _perform_pytest_unconfigure_(config): | |
| 2341 2442 | 
             
                            the_html_r = the_html_r.replace(
         | 
| 2342 2443 | 
             
                                ph_link, "%s and %s" % (sb_link, ph_link)
         | 
| 2343 2444 | 
             
                            )
         | 
| 2445 | 
            +
                            the_html_r = the_html_r.replace(
         | 
| 2446 | 
            +
                                "findAll('.collapsible", "//findAll('.collapsible"
         | 
| 2447 | 
            +
                            )
         | 
| 2344 2448 | 
             
                            the_html_r = the_html_r.replace(
         | 
| 2345 2449 | 
             
                                "mediaName.innerText", "//mediaName.innerText"
         | 
| 2346 2450 | 
             
                            )
         | 
| @@ -2358,6 +2462,12 @@ def _perform_pytest_unconfigure_(config): | |
| 2358 2462 | 
             
                            )
         | 
| 2359 2463 | 
             
                            with open(html_report_path, "w", encoding="utf-8") as f:
         | 
| 2360 2464 | 
             
                                f.write(the_html_r)  # Finalize the HTML report
         | 
| 2465 | 
            +
                            with suppress(Exception):
         | 
| 2466 | 
            +
                                shared_utils.make_writable(html_report_path)
         | 
| 2467 | 
            +
                            with open(html_report_path_copy, "w", encoding="utf-8") as f:
         | 
| 2468 | 
            +
                                f.write(the_html_r)  # Finalize the HTML report copy
         | 
| 2469 | 
            +
                            with suppress(Exception):
         | 
| 2470 | 
            +
                                shared_utils.make_writable(html_report_path_copy)
         | 
| 2361 2471 | 
             
                except KeyboardInterrupt:
         | 
| 2362 2472 | 
             
                    pass
         | 
| 2363 2473 | 
             
                except Exception:
         | 
| @@ -568,6 +568,34 @@ def SB( | |
| 568 568 | 
             
                            break
         | 
| 569 569 | 
             
                        count += 1
         | 
| 570 570 | 
             
                user_agent = agent
         | 
| 571 | 
            +
                found_bl = None
         | 
| 572 | 
            +
                if binary_location is None and "--binary-location" in arg_join:
         | 
| 573 | 
            +
                    count = 0
         | 
| 574 | 
            +
                    for arg in sys_argv:
         | 
| 575 | 
            +
                        if arg.startswith("--binary-location="):
         | 
| 576 | 
            +
                            found_bl = arg.split("--binary-location=")[1]
         | 
| 577 | 
            +
                            break
         | 
| 578 | 
            +
                        elif arg == "--binary-location" and len(sys_argv) > count + 1:
         | 
| 579 | 
            +
                            found_bl = sys_argv[count + 1]
         | 
| 580 | 
            +
                            if found_bl.startswith("-"):
         | 
| 581 | 
            +
                                found_bl = None
         | 
| 582 | 
            +
                            break
         | 
| 583 | 
            +
                        count += 1
         | 
| 584 | 
            +
                    if found_bl:
         | 
| 585 | 
            +
                        binary_location = found_bl
         | 
| 586 | 
            +
                if binary_location is None and "--bl=" in arg_join:
         | 
| 587 | 
            +
                    for arg in sys_argv:
         | 
| 588 | 
            +
                        if arg.startswith("--bl="):
         | 
| 589 | 
            +
                            binary_location = arg.split("--bl=")[1]
         | 
| 590 | 
            +
                            break
         | 
| 591 | 
            +
                if (
         | 
| 592 | 
            +
                    binary_location
         | 
| 593 | 
            +
                    and binary_location.lower() == "chs"
         | 
| 594 | 
            +
                    and browser == "chrome"
         | 
| 595 | 
            +
                ):
         | 
| 596 | 
            +
                    headless = True
         | 
| 597 | 
            +
                    headless1 = False
         | 
| 598 | 
            +
                    headless2 = False
         | 
| 571 599 | 
             
                recorder_mode = False
         | 
| 572 600 | 
             
                if recorder_ext:
         | 
| 573 601 | 
             
                    recorder_mode = True
         | 
| @@ -1256,6 +1284,19 @@ def SB( | |
| 1256 1284 | 
             
                        print(traceback.format_exc().strip())
         | 
| 1257 1285 | 
             
                        if test and not test_passed:
         | 
| 1258 1286 | 
             
                            print("********** ERROR: The test AND the tearDown() FAILED!")
         | 
| 1287 | 
            +
                    if (
         | 
| 1288 | 
            +
                        hasattr(sb_config, "_virtual_display")
         | 
| 1289 | 
            +
                        and sb_config._virtual_display
         | 
| 1290 | 
            +
                        and hasattr(sb_config._virtual_display, "stop")
         | 
| 1291 | 
            +
                    ):
         | 
| 1292 | 
            +
                        try:
         | 
| 1293 | 
            +
                            sb_config._virtual_display.stop()
         | 
| 1294 | 
            +
                            sb_config._virtual_display = None
         | 
| 1295 | 
            +
                            sb_config.headless_active = False
         | 
| 1296 | 
            +
                        except AttributeError:
         | 
| 1297 | 
            +
                            pass
         | 
| 1298 | 
            +
                        except Exception:
         | 
| 1299 | 
            +
                            pass
         | 
| 1259 1300 | 
             
                    end_time = time.time()
         | 
| 1260 1301 | 
             
                    run_time = end_time - start_time
         | 
| 1261 1302 | 
             
                    sb_config = sb_config_backup
         | 
| @@ -397,6 +397,7 @@ class SeleniumBrowser(Plugin): | |
| 397 397 | 
             
                    parser.addoption(
         | 
| 398 398 | 
             
                        "--binary_location",
         | 
| 399 399 | 
             
                        "--binary-location",
         | 
| 400 | 
            +
                        "--bl",
         | 
| 400 401 | 
             
                        action="store",
         | 
| 401 402 | 
             
                        dest="binary_location",
         | 
| 402 403 | 
             
                        default=None,
         | 
| @@ -1202,6 +1203,14 @@ class SeleniumBrowser(Plugin): | |
| 1202 1203 | 
             
                    test.test.extension_dir = self.options.extension_dir
         | 
| 1203 1204 | 
             
                    test.test.disable_features = self.options.disable_features
         | 
| 1204 1205 | 
             
                    test.test.binary_location = self.options.binary_location
         | 
| 1206 | 
            +
                    if (
         | 
| 1207 | 
            +
                        test.test.binary_location
         | 
| 1208 | 
            +
                        and test.test.binary_location.lower() == "chs"
         | 
| 1209 | 
            +
                        and test.test.browser == "chrome"
         | 
| 1210 | 
            +
                    ):
         | 
| 1211 | 
            +
                        test.test.headless = True
         | 
| 1212 | 
            +
                        test.test.headless1 = False
         | 
| 1213 | 
            +
                        test.test.headless2 = False
         | 
| 1205 1214 | 
             
                    test.test.driver_version = self.options.driver_version
         | 
| 1206 1215 | 
             
                    test.test.page_load_strategy = self.options.page_load_strategy
         | 
| 1207 1216 | 
             
                    test.test.chromium_arg = self.options.chromium_arg
         | 
| @@ -31,12 +31,12 @@ class ContraDict(dict): | |
| 31 31 |  | 
| 32 32 | 
             
                def __init__(self, *args, **kwargs):
         | 
| 33 33 | 
             
                    super().__init__()
         | 
| 34 | 
            -
                    silent = kwargs.pop("silent", False)
         | 
| 34 | 
            +
                    # silent = kwargs.pop("silent", False)
         | 
| 35 35 | 
             
                    _ = dict(*args, **kwargs)
         | 
| 36 36 |  | 
| 37 37 | 
             
                    super().__setattr__("__dict__", self)
         | 
| 38 38 | 
             
                    for k, v in _.items():
         | 
| 39 | 
            -
                        _check_key(k, self, False,  | 
| 39 | 
            +
                        _check_key(k, self, False, True)
         | 
| 40 40 | 
             
                        super().__setitem__(k, _wrap(self.__class__, v))
         | 
| 41 41 |  | 
| 42 42 | 
             
                def __setitem__(self, key, value):
         | 
| @@ -90,7 +90,7 @@ _warning_names_message = """\n\ | |
| 90 90 |  | 
| 91 91 |  | 
| 92 92 | 
             
            def _check_key(
         | 
| 93 | 
            -
                key: str, mapping: _Mapping, boolean: bool = False, silent= | 
| 93 | 
            +
                key: str, mapping: _Mapping, boolean: bool = False, silent=True
         | 
| 94 94 | 
             
            ):
         | 
| 95 95 | 
             
                """Checks `key` and warns if needed.
         | 
| 96 96 | 
             
                :param key:
         | 
| @@ -10,6 +10,7 @@ import pathlib | |
| 10 10 | 
             
            import pickle
         | 
| 11 11 | 
             
            import re
         | 
| 12 12 | 
             
            import shutil
         | 
| 13 | 
            +
            import time
         | 
| 13 14 | 
             
            import urllib.parse
         | 
| 14 15 | 
             
            import urllib.request
         | 
| 15 16 | 
             
            import warnings
         | 
| @@ -30,8 +31,6 @@ def get_registered_instances(): | |
| 30 31 |  | 
| 31 32 |  | 
| 32 33 | 
             
            def deconstruct_browser():
         | 
| 33 | 
            -
                import time
         | 
| 34 | 
            -
             | 
| 35 34 | 
             
                for _ in __registered__instances__:
         | 
| 36 35 | 
             
                    if not _.stopped:
         | 
| 37 36 | 
             
                        _.stop()
         | 
| @@ -117,8 +116,13 @@ class Browser: | |
| 117 116 | 
             
                            port=port,
         | 
| 118 117 | 
             
                            **kwargs,
         | 
| 119 118 | 
             
                        )
         | 
| 120 | 
            -
                     | 
| 121 | 
            -
             | 
| 119 | 
            +
                    try:
         | 
| 120 | 
            +
                        instance = cls(config)
         | 
| 121 | 
            +
                        await instance.start()
         | 
| 122 | 
            +
                    except Exception:
         | 
| 123 | 
            +
                        time.sleep(0.15)
         | 
| 124 | 
            +
                        instance = cls(config)
         | 
| 125 | 
            +
                        await instance.start()
         | 
| 122 126 | 
             
                    return instance
         | 
| 123 127 |  | 
| 124 128 | 
             
                def __init__(self, config: Config, **kwargs):
         | 
| @@ -379,8 +383,6 @@ class Browser: | |
| 379 383 | 
             
                                --------------------------------
         | 
| 380 384 | 
             
                                Failed to connect to the browser
         | 
| 381 385 | 
             
                                --------------------------------
         | 
| 382 | 
            -
                                Possibly because you are running as "root".
         | 
| 383 | 
            -
                                If so, you may need to use no_sandbox=True.
         | 
| 384 386 | 
             
                                """
         | 
| 385 387 | 
             
                            )
         | 
| 386 388 | 
             
                        )
         | 
| @@ -84,6 +84,9 @@ def __activate_virtual_display_as_needed( | |
| 84 84 | 
             
                                        "\nX11 display failed! Will use regular xvfb!"
         | 
| 85 85 | 
             
                                    )
         | 
| 86 86 | 
             
                                    __activate_standard_virtual_display()
         | 
| 87 | 
            +
                                else:
         | 
| 88 | 
            +
                                    sb_config._virtual_display = _xvfb_display
         | 
| 89 | 
            +
                                    sb_config.headless_active = True
         | 
| 87 90 | 
             
                            except Exception as e:
         | 
| 88 91 | 
             
                                if hasattr(e, "msg"):
         | 
| 89 92 | 
             
                                    print("\n" + str(e.msg))
         | 
| @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            from __future__ import annotations
         | 
| 2 2 | 
             
            import asyncio
         | 
| 3 | 
            -
            import json
         | 
| 4 3 | 
             
            import logging
         | 
| 5 4 | 
             
            import pathlib
         | 
| 6 5 | 
             
            import secrets
         | 
| @@ -387,18 +386,16 @@ class Element: | |
| 387 386 |  | 
| 388 387 | 
             
                async def get_js_attributes_async(self):
         | 
| 389 388 | 
             
                    return ContraDict(
         | 
| 390 | 
            -
                         | 
| 391 | 
            -
                             | 
| 392 | 
            -
             | 
| 393 | 
            -
             | 
| 394 | 
            -
             | 
| 395 | 
            -
             | 
| 396 | 
            -
                                 | 
| 389 | 
            +
                        await self.apply(
         | 
| 390 | 
            +
                            """
         | 
| 391 | 
            +
                            function (e) {
         | 
| 392 | 
            +
                                let o = {}
         | 
| 393 | 
            +
                                for(let k in e){
         | 
| 394 | 
            +
                                    o[k] = e[k]
         | 
| 395 | 
            +
                                }
         | 
| 396 | 
            +
                                return o
         | 
| 397 397 | 
             
                            }
         | 
| 398 | 
            -
                             | 
| 399 | 
            -
                        }
         | 
| 400 | 
            -
                        """
         | 
| 401 | 
            -
                            )
         | 
| 398 | 
            +
                            """
         | 
| 402 399 | 
             
                        )
         | 
| 403 400 | 
             
                    )
         | 
| 404 401 |  | 
| @@ -441,12 +438,15 @@ class Element: | |
| 441 438 | 
             
                            )
         | 
| 442 439 | 
             
                        )
         | 
| 443 440 | 
             
                    )
         | 
| 444 | 
            -
                     | 
| 445 | 
            -
                        if  | 
| 446 | 
            -
                             | 
| 447 | 
            -
             | 
| 448 | 
            -
             | 
| 449 | 
            -
                         | 
| 441 | 
            +
                    try:
         | 
| 442 | 
            +
                        if result and result[0]:
         | 
| 443 | 
            +
                            if return_by_value:
         | 
| 444 | 
            +
                                return result[0].value
         | 
| 445 | 
            +
                            return result[0]
         | 
| 446 | 
            +
                        elif result[1]:
         | 
| 447 | 
            +
                            return result[1]
         | 
| 448 | 
            +
                    except Exception:
         | 
| 449 | 
            +
                        return self
         | 
| 450 450 |  | 
| 451 451 | 
             
                async def get_position_async(self, abs=False) -> Position:
         | 
| 452 452 | 
             
                    if not self.parent or not self.object_id:
         | 
| @@ -883,6 +883,8 @@ class Element: | |
| 883 883 | 
             
                    self,
         | 
| 884 884 | 
             
                    duration: typing.Union[float, int] = 0.5,
         | 
| 885 885 | 
             
                    color: typing.Optional[str] = "EE4488",
         | 
| 886 | 
            +
                    x_offset: typing.Union[float, int] = 0,
         | 
| 887 | 
            +
                    y_offset: typing.Union[float, int] = 0,
         | 
| 886 888 | 
             
                ):
         | 
| 887 889 | 
             
                    """
         | 
| 888 890 | 
             
                    Displays for a short time a red dot on the element.
         | 
| @@ -910,8 +912,8 @@ class Element: | |
| 910 912 | 
             
                        "width:8px;height:8px;border-radius:50%;background:#{};"
         | 
| 911 913 | 
             
                        "animation:show-pointer-ani {:.2f}s ease 1;"
         | 
| 912 914 | 
             
                    ).format(
         | 
| 913 | 
            -
                        pos.center[0] - 4,  # -4 to account for  | 
| 914 | 
            -
                        pos.center[1] - 4,
         | 
| 915 | 
            +
                        pos.center[0] + x_offset - 4,  # -4 to account for the circle
         | 
| 916 | 
            +
                        pos.center[1] + y_offset - 4,  # -4 to account for the circle
         | 
| 915 917 | 
             
                        color,
         | 
| 916 918 | 
             
                        duration,
         | 
| 917 919 | 
             
                    )
         | 
| @@ -8,6 +8,8 @@ import sys | |
| 8 8 | 
             
            import time
         | 
| 9 9 | 
             
            import zipfile
         | 
| 10 10 | 
             
            from contextlib import suppress
         | 
| 11 | 
            +
            from seleniumbase.console_scripts import sb_install
         | 
| 12 | 
            +
            from seleniumbase.fixtures import shared_utils
         | 
| 11 13 |  | 
| 12 14 | 
             
            logger = logging.getLogger(__name__)
         | 
| 13 15 | 
             
            IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux"))
         | 
| @@ -106,7 +108,14 @@ class Patcher(object): | |
| 106 108 | 
             
                    release = self.fetch_release_number()
         | 
| 107 109 | 
             
                    self.version_main = release.split(".")[0]
         | 
| 108 110 | 
             
                    self.version_full = release
         | 
| 109 | 
            -
                     | 
| 111 | 
            +
                    if int(self.version_main) < 115:
         | 
| 112 | 
            +
                        self.unzip_package(self.fetch_package())
         | 
| 113 | 
            +
                    else:
         | 
| 114 | 
            +
                        sb_install.main(
         | 
| 115 | 
            +
                            override="chromedriver %s" % self.version_main,
         | 
| 116 | 
            +
                            intel_for_uc=shared_utils.is_arm_mac(),
         | 
| 117 | 
            +
                            force_uc=True,
         | 
| 118 | 
            +
                        )
         | 
| 110 119 | 
             
                    return self.patch()
         | 
| 111 120 |  | 
| 112 121 | 
             
                def patch(self):
         | 
| @@ -121,6 +130,12 @@ class Patcher(object): | |
| 121 130 | 
             
                        path += "_%s" % self.version_main
         | 
| 122 131 | 
             
                    path = path.upper()
         | 
| 123 132 | 
             
                    logger.debug("Getting release number from %s" % path)
         | 
| 133 | 
            +
                    if self.version_main and int(self.version_main) > 114:
         | 
| 134 | 
            +
                        return (
         | 
| 135 | 
            +
                            sb_install.get_cft_latest_version_from_milestone(
         | 
| 136 | 
            +
                                str(self.version_main)
         | 
| 137 | 
            +
                            )
         | 
| 138 | 
            +
                        )
         | 
| 124 139 | 
             
                    return urlopen(self.url_repo + path).read().decode()
         | 
| 125 140 |  | 
| 126 141 | 
             
                def fetch_package(self):
         | 
| @@ -187,7 +202,7 @@ class Patcher(object): | |
| 187 202 | 
             
                    with io.open(executable_path, "rb") as fh:
         | 
| 188 203 | 
             
                        if re.search(
         | 
| 189 204 | 
             
                            b"window.cdc_adoQpoasnfa76pfcZLmcfl_"
         | 
| 190 | 
            -
                            b"(Array|Promise|Symbol|Object|Proxy|JSON)",
         | 
| 205 | 
            +
                            b"(Array|Promise|Symbol|Object|Proxy|JSON|Window)",
         | 
| 191 206 | 
             
                            fh.read()
         | 
| 192 207 | 
             
                        ):
         | 
| 193 208 | 
             
                            return False
         | 
| @@ -210,14 +225,14 @@ class Patcher(object): | |
| 210 225 | 
             
                        file_bin = fh.read()
         | 
| 211 226 | 
             
                        file_bin = re.sub(
         | 
| 212 227 | 
             
                            b"window\\.cdc_[a-zA-Z0-9]{22}_"
         | 
| 213 | 
            -
                            b"(Array|Promise|Symbol|Object|Proxy|JSON)"
         | 
| 214 | 
            -
                            b" | 
| 228 | 
            +
                            b"(Array|Promise|Symbol|Object|Proxy|JSON|Window) "
         | 
| 229 | 
            +
                            b"= window\\.(Array|Promise|Symbol|Object|Proxy|JSON|Window);",
         | 
| 215 230 | 
             
                            gen_js_whitespaces,
         | 
| 216 231 | 
             
                            file_bin,
         | 
| 217 232 | 
             
                        )
         | 
| 218 233 | 
             
                        file_bin = re.sub(
         | 
| 219 234 | 
             
                            b"window\\.cdc_[a-zA-Z0-9]{22}_"
         | 
| 220 | 
            -
                            b"(Array|Promise|Symbol|Object|Proxy|JSON) \\|\\|",
         | 
| 235 | 
            +
                            b"(Array|Promise|Symbol|Object|Proxy|JSON|Window) \\|\\|",
         | 
| 221 236 | 
             
                            gen_js_whitespaces,
         | 
| 222 237 | 
             
                            file_bin,
         | 
| 223 238 | 
             
                        )
         |