"""This is the pytest configuration file for setting test options."""
import colorama
import os
import pytest
import sys
import time
from contextlib import suppress
from seleniumbase import config as sb_config
from seleniumbase.config import settings
from seleniumbase.core import log_helper
from seleniumbase.fixtures import constants
from seleniumbase.fixtures import shared_utils

is_windows = shared_utils.is_windows()
python3_11_or_newer = False
if sys.version_info >= (3, 11):
    python3_11_or_newer = True
py311_patch2 = constants.PatchPy311.PATCH
sys_argv = sys.argv
full_time = None
pytest_plugins = ["pytester"]  # Adds the "testdir" fixture


def pytest_addoption(parser):
    """This plugin adds the following command-line options to pytest:
    --browser=BROWSER  (The web browser to use. Default: "chrome".)
    --chrome  (Shortcut for "--browser=chrome". Default.)
    --edge  (Shortcut for "--browser=edge".)
    --firefox  (Shortcut for "--browser=firefox".)
    --safari  (Shortcut for "--browser=safari".)
    --settings-file=FILE  (Override default SeleniumBase settings.)
    --env=ENV  (Set the test env. Access with "self.env" in tests.)
    --account=STR  (Set account. Access with "self.account" in tests.)
    --data=STRING  (Extra test data. Access with "self.data" in tests.)
    --var1=STRING  (Extra test data. Access with "self.var1" in tests.)
    --var2=STRING  (Extra test data. Access with "self.var2" in tests.)
    --var3=STRING  (Extra test data. Access with "self.var3" in tests.)
    --variables=DICT  (Extra test data. Access with "self.variables".)
    --user-data-dir=DIR  (Set the Chrome user data directory to use.)
    --protocol=PROTOCOL  (The Selenium Grid protocol: http|https.)
    --server=SERVER  (The Selenium Grid server/IP used for tests.)
    --port=PORT  (The Selenium Grid port used by the test server.)
    --cap-file=FILE  (The web browser's desired capabilities to use.)
    --cap-string=STRING  (The web browser's desired capabilities to use.)
    --proxy=SERVER:PORT  (Connect to a proxy server:port as tests are running)
    --proxy=USERNAME:PASSWORD@SERVER:PORT  (Use an authenticated proxy server)
    --proxy-bypass-list=STRING (";"-separated hosts to bypass, Eg "*.foo.com")
    --proxy-pac-url=URL  (Connect to a proxy server using a PAC_URL.pac file.)
    --proxy-pac-url=USERNAME:PASSWORD@URL  (Authenticated proxy with PAC URL.)
    --proxy-driver  (If a driver download is needed, will use: --proxy=PROXY.)
    --multi-proxy  (Allow multiple authenticated proxies when multi-threaded.)
    --agent=STRING  (Modify the web browser's User-Agent string.)
    --mobile  (Use the mobile device emulator while running tests.)
    --metrics=STRING  (Set mobile metrics: "CSSWidth,CSSHeight,PixelRatio".)
    --chromium-arg="ARG=N,ARG2" (Set Chromium args, ","-separated, no spaces.)
    --firefox-arg="ARG=N,ARG2" (Set Firefox args, comma-separated, no spaces.)
    --firefox-pref=SET  (Set a Firefox preference:value set, comma-separated.)
    --extension-zip=ZIP  (Load a Chrome Extension .zip|.crx, comma-separated.)
    --extension-dir=DIR  (Load a Chrome Extension directory, comma-separated.)
    --disable-features="F1,F2" (Disable features, comma-separated, no spaces.)
    --binary-location=PATH  (Set path of the Chromium browser binary to use.)
    --driver-version=VER  (Set the chromedriver or uc_driver version to use.)
    --sjw  (Skip JS Waits for readyState to be "complete" or Angular to load.)
    --wfa  (Wait for AngularJS to be done loading after specific web actions.)
    --pls=PLS  (Set pageLoadStrategy on Chrome: "normal", "eager", or "none".)
    --headless  (The default headless mode. Linux uses this mode by default.)
    --headless1  (Use Chrome's old headless mode. Fast, but has limitations.)
    --headless2  (Use Chrome's new headless mode, which supports extensions.)
    --headed  (Run tests in headed/GUI mode on Linux OS, where not default.)
    --xvfb  (Run tests using the Xvfb virtual display server on Linux OS.)
    --xvfb-metrics=STRING  (Set Xvfb display size on Linux: "Width,Height".)
    --locale=LOCALE_CODE  (Set the Language Locale Code for the web browser.)
    --interval=SECONDS  (The autoplay interval for presentations & tour steps)
    --start-page=URL  (The starting URL for the web browser when tests begin.)
    --archive-logs  (Archive existing log files instead of deleting them.)
    --archive-downloads  (Archive old downloads instead of deleting them.)
    --time-limit=SECONDS  (Safely fail any test that exceeds the time limit.)
    --slow  (Slow down the automation. Faster than using Demo Mode.)
    --demo  (Slow down and visually see test actions as they occur.)
    --demo-sleep=SECONDS  (Set the wait time after Slow & Demo Mode actions.)
    --highlights=NUM  (Number of highlight animations for Demo Mode actions.)
    --message-duration=SECONDS  (The time length for Messenger alerts.)
    --check-js  (Check for JavaScript errors after page loads.)
    --ad-block  (Block some types of display ads from loading.)
    --host-resolver-rules=RULES  (Set host-resolver-rules, comma-separated.)
    --block-images  (Block images from loading during tests.)
    --do-not-track  (Indicate to websites that you don't want to be tracked.)
    --verify-delay=SECONDS  (The delay before MasterQA verification checks.)
    --ee / --esc-end  (Lets the user end the current test via the ESC key.)
    --recorder  (Enables the Recorder for turning browser actions into code.)
    --rec-behave  (Same as Recorder Mode, but also generates behave-gherkin.)
    --rec-sleep  (If the Recorder is enabled, also records self.sleep calls.)
    --rec-print  (If the Recorder is enabled, prints output after tests end.)
    --disable-cookies  (Disable Cookies on websites. Pages might break!)
    --disable-js  (Disable JavaScript on websites. Pages might break!)
    --disable-csp  (Disable the Content Security Policy of websites.)
    --disable-ws  (Disable Web Security on Chromium-based browsers.)
    --enable-ws  (Enable Web Security on Chromium-based browsers.)
    --enable-sync  (Enable "Chrome Sync" on websites.)
    --uc | --undetected  (Use undetected-chromedriver to evade bot-detection.)
    --uc-cdp-events  (Capture CDP events when running in "--undetected" mode.)
    --log-cdp  ("goog:loggingPrefs", {"performance": "ALL", "browser": "ALL"})
    --remote-debug  (Sync to Chrome Remote Debugger chrome://inspect/#devices)
    --ftrace | --final-trace  (Debug Mode after each test. Don't use with CI!)
    --dashboard  (Enable the SeleniumBase Dashboard. Saved at: dashboard.html)
    --dash-title=STRING  (Set the title shown for the generated dashboard.)
    --enable-3d-apis  (Enables WebGL and 3D APIs.)
    --swiftshader  (Chrome "--use-gl=angle" / "--use-angle=swiftshader-webgl")
    --incognito  (Enable Chrome's Incognito mode.)
    --guest  (Enable Chrome's Guest mode.)
    --dark  (Enable Chrome's Dark mode.)
    --devtools  (Open Chrome's DevTools when the browser opens.)
    --rs | --reuse-session  (Reuse browser session for all tests.)
    --rcs | --reuse-class-session  (Reuse session for tests in class.)
    --crumbs  (Delete all cookies between tests reusing a session.)
    --disable-beforeunload  (Disable the "beforeunload" event on Chrome.)
    --window-position=X,Y  (Set the browser's starting window position.)
    --window-size=WIDTH,HEIGHT  (Set the browser's starting window size.)
    --maximize  (Start tests with the browser window maximized.)
    --screenshot  (Save a screenshot at the end of each test.)
    --no-screenshot  (No screenshots saved unless tests directly ask it.)
    --visual-baseline  (Set the visual baseline for Visual/Layout tests.)
    --wire  (Use selenium-wire's webdriver for replacing selenium webdriver.)
    --external-pdf  (Set Chromium "plugins.always_open_pdf_externally":True.)
    --timeout-multiplier=MULTIPLIER  (Multiplies the default timeout values.)
    --list-fail-page  (After each failing test, list the URL of the failure.)
    """
    c1 = ""
    c2 = ""
    c3 = ""
    cr = ""
    if "linux" not in sys.platform:
        # This will be seen when typing "pytest --help" on the command line.
        c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX
        c2 = colorama.Fore.BLUE + colorama.Back.LIGHTGREEN_EX
        c3 = colorama.Fore.MAGENTA + colorama.Back.LIGHTYELLOW_EX
        cr = colorama.Style.RESET_ALL
    s_str = "SeleniumBase"
    s_str = s_str.replace("SeleniumBase", c1 + "Selenium" + c2 + "Base" + cr)
    s_str = s_str + cr + " " + c3 + "command-line options for pytest" + cr
    parser = parser.getgroup("SeleniumBase", s_str)
    parser.addoption(
        "--browser",
        action="store",
        dest="browser",
        type=str.lower,
        choices=constants.ValidBrowsers.valid_browsers,
        default=constants.Browser.GOOGLE_CHROME,
        help="""Specifies the web browser to use. Default: Chrome.
                Examples: (--browser=edge OR --browser=firefox)""",
    )
    parser.addoption(
        "--chrome",
        action="store_true",
        dest="use_chrome",
        default=False,
        help="""Shortcut for --browser=chrome (Default)""",
    )
    parser.addoption(
        "--edge",
        action="store_true",
        dest="use_edge",
        default=False,
        help="""Shortcut for --browser=edge""",
    )
    parser.addoption(
        "--firefox",
        action="store_true",
        dest="use_firefox",
        default=False,
        help="""Shortcut for --browser=firefox""",
    )
    parser.addoption(
        "--ie",
        action="store_true",
        dest="use_ie",
        default=False,
        help="""Shortcut for --browser=ie""",
    )
    parser.addoption(
        "--safari",
        action="store_true",
        dest="use_safari",
        default=False,
        help="""Shortcut for --browser=safari""",
    )
    parser.addoption(
        "--cft",
        action="store_true",
        dest="use_cft",
        default=False,
        help="""Shortcut for using `Chrome for Testing`""",
    )
    parser.addoption(
        "--chs",
        action="store_true",
        dest="use_chs",
        default=False,
        help="""Shortcut for using `Chrome-Headless-Shell`""",
    )
    parser.addoption(
        "--with-selenium",
        action="store_true",
        dest="with_selenium",
        default=True,
        help="""(DEPRECATED) Start tests with an open web browser.
                (This is ALWAYS True now when importing BaseCase)""",
    )
    parser.addoption(
        "--env",
        action="store",
        dest="environment",
        type=str.lower,
        choices=(
            constants.Environment.QA,
            constants.Environment.RC,
            constants.Environment.STAGING,
            constants.Environment.DEVELOP,
            constants.Environment.PRODUCTION,
            constants.Environment.PERFORMANCE,
            constants.Environment.REPLICA,
            constants.Environment.FEDRAMP,
            constants.Environment.OFFLINE,
            constants.Environment.ONLINE,
            constants.Environment.MASTER,
            constants.Environment.REMOTE,
            constants.Environment.LEGACY,
            constants.Environment.LOCAL,
            constants.Environment.ALPHA,
            constants.Environment.BETA,
            constants.Environment.DEMO,
            constants.Environment.GDPR,
            constants.Environment.MAIN,
            constants.Environment.TEST,
            constants.Environment.GOV,
            constants.Environment.NEW,
            constants.Environment.OLD,
            constants.Environment.UAT,
        ),
        default=constants.Environment.TEST,
        help="""This option sets a test env from a list of choices.
                Access using "self.env" or "self.environment".""",
    )
    parser.addoption(
        "--account",
        dest="account",
        default=None,
        help="""This option sets a test account string.
                In tests, use "self.account" to get the value.""",
    )
    parser.addoption(
        "--data",
        dest="data",
        default=None,
        help="Extra data to pass to tests from the command line.",
    )
    parser.addoption(
        "--var1",
        dest="var1",
        default=None,
        help="Extra data to pass to tests from the command line.",
    )
    parser.addoption(
        "--var2",
        dest="var2",
        default=None,
        help="Extra data to pass to tests from the command line.",
    )
    parser.addoption(
        "--var3",
        dest="var3",
        default=None,
        help="Extra data to pass to tests from the command line.",
    )
    parser.addoption(
        "--variables",
        dest="variables",
        default=None,
        help="""A var dict to pass to tests from the command line.
                Example usage:
                ----------------------------------------------
                Option: --variables='{"special":123}'
                Access: self.variables["special"]  # (123)
                ----------------------------------------------
                Option: --variables='{"color":"red","num":42}'
                Access: self.variables["color"]  # ("red")
                Access: self.variables["num"]  # (42)
                ----------------------------------------------""",
    )
    parser.addoption(
        "--cap_file",
        "--cap-file",
        dest="cap_file",
        default=None,
        help="""The file that stores browser desired capabilities
                for BrowserStack, Sauce Labs, or other grids.""",
    )
    parser.addoption(
        "--cap_string",
        "--cap-string",
        dest="cap_string",
        default=None,
        help="""The string that stores browser desired capabilities
                for BrowserStack, Sauce Labs, or other grids.
                Enclose cap-string in single quotes.
                Enclose parameter keys in double quotes.
                Example: --cap-string='{"name":"test1","v":"42"}'""",
    )
    parser.addoption(
        "--settings_file",
        "--settings-file",
        "--settings",
        action="store",
        dest="settings_file",
        default=None,
        help="""The file that stores key/value pairs for
                overriding values in the
                seleniumbase/config/settings.py file.""",
    )
    parser.addoption(
        "--user_data_dir",
        "--user-data-dir",
        dest="user_data_dir",
        default=None,
        help="""The Chrome User Data Directory to use. (Profile)
                If the directory doesn't exist, it'll be created.""",
    )
    parser.addoption(
        "--with-testing_base",
        "--with-testing-base",
        action="store_true",
        dest="with_testing_base",
        default=True,
        help="""(DEPRECATED) - This option is always enabled now.
                Use for saving logs & screenshots when tests fail.
                The following options are now active by default
                with --with-testing_base (which is always on now):
                --with-screen_shots ,
                --with-basic_test_info ,
                --with-page_source
            """,
    )
    parser.addoption(
        "--log_path",
        "--log-path",
        dest="log_path",
        default=constants.Logs.LATEST + "/",
        help="""(DEPRECATED) - This value is NOT EDITABLE anymore.
                Log files are saved to the "latest_logs/" folder.""",
    )
    parser.addoption(
        "--archive_logs",
        "--archive-logs",
        action="store_true",
        dest="archive_logs",
        default=False,
        help="Archive old log files instead of deleting them.",
    )
    parser.addoption(
        "--archive_downloads",
        "--archive-downloads",
        action="store_true",
        dest="archive_downloads",
        default=False,
        help="Archive old downloads instead of deleting them.",
    )
    parser.addoption(
        "--sjw",
        "--skip_js_waits",
        "--skip-js-waits",
        action="store_true",
        dest="skip_js_waits",
        default=False,
        help="""Skip all calls to wait_for_ready_state_complete()
                and wait_for_angularjs(), which are part of many
                SeleniumBase methods for improving reliability.""",
    )
    parser.addoption(
        "--wfa",
        "--wait_for_angularjs",
        "--wait-for-angularjs",
        action="store_true",
        dest="wait_for_angularjs",
        default=False,
        help="""Add waiting for AngularJS. (The default setting
                was changed to no longer wait for AngularJS to
                finish loading as an extra JavaScript call.)""",
    )
    parser.addoption(
        "--with-db_reporting",
        "--with-db-reporting",
        action="store_true",
        dest="with_db_reporting",
        default=False,
        help="Use to record test data in the MySQL database.",
    )
    parser.addoption(
        "--database_env",
        "--database-env",
        action="store",
        dest="database_env",
        choices=(
            constants.Environment.QA,
            constants.Environment.RC,
            constants.Environment.STAGING,
            constants.Environment.DEVELOP,
            constants.Environment.PRODUCTION,
            constants.Environment.PERFORMANCE,
            constants.Environment.REPLICA,
            constants.Environment.FEDRAMP,
            constants.Environment.OFFLINE,
            constants.Environment.ONLINE,
            constants.Environment.MASTER,
            constants.Environment.REMOTE,
            constants.Environment.LEGACY,
            constants.Environment.LOCAL,
            constants.Environment.ALPHA,
            constants.Environment.BETA,
            constants.Environment.DEMO,
            constants.Environment.GDPR,
            constants.Environment.MAIN,
            constants.Environment.TEST,
            constants.Environment.GOV,
            constants.Environment.NEW,
            constants.Environment.OLD,
            constants.Environment.UAT,
        ),
        default=constants.Environment.TEST,
        help="The database environment to run the tests in.",
    )
    parser.addoption(
        "--with-s3_logging",
        "--with-s3-logging",
        action="store_true",
        dest="with_s3_logging",
        default=False,
        help="Use to save test log files in Amazon S3.",
    )
    parser.addoption(
        "--with-screen_shots",
        "--with-screen-shots",
        action="store_true",
        dest="with_screen_shots",
        default=False,
        help="""(DEPRECATED) - Screenshots are always saved now.
                This option saves screenshots during test failures.
                Screenshots are saved in the "latest_logs/" folder.
                (Automatically on when using --with-testing_base)""",
    )
    parser.addoption(
        "--with-basic_test_info",
        "--with-basic-test-info",
        action="store_true",
        dest="with_basic_test_info",
        default=False,
        help="""(DEPRECATED) - Info files are always saved now.
                This option saves basic test info on test failures.
                These files are saved in the "latest_logs/" folder.
                (Automatically on when using --with-testing_base)""",
    )
    parser.addoption(
        "--with-page_source",
        "--with-page-source",
        action="store_true",
        dest="with_page_source",
        default=False,
        help="""(DEPRECATED) - Page source is saved by default.
                This option saves page source files on test failures.
                (Automatically on when using --with-testing_base)""",
    )
    parser.addoption(
        "--protocol",
        action="store",
        dest="protocol",
        choices=(
            constants.Protocol.HTTP,
            constants.Protocol.HTTPS,
        ),
        default=constants.Protocol.HTTP,
        help="""Designates the Selenium Grid protocol to use.
                Default: http.""",
    )
    parser.addoption(
        "--server",
        action="store",
        dest="servername",
        default="localhost",
        help="""Designates the Selenium Grid server to use.
                Use "127.0.0.1" to connect to a localhost Grid.
                If unset or set to "localhost", Grid isn't used.
                Default: "localhost".""",
    )
    parser.addoption(
        "--port",
        action="store",
        dest="port",
        default="4444",
        help="""Designates the Selenium Grid port to use.
                Default: 4444. (If 443, protocol becomes "https")""",
    )
    parser.addoption(
        "--proxy",
        "--proxy-server",
        "--proxy-string",
        action="store",
        dest="proxy_string",
        default=None,
        help="""Designates the proxy server:port to use.
                Format: servername:port.  OR
                        username:password@servername:port  OR
                        A dict key from proxy_list.PROXY_LIST
                Default: None.""",
    )
    parser.addoption(
        "--proxy-bypass-list",
        "--proxy_bypass_list",
        action="store",
        dest="proxy_bypass_list",
        default=None,
        help="""Designates the hosts, domains, and/or IP addresses
                to bypass when using a proxy server with "--proxy".
                Format: A ";"-separated string.
                Example usage:
                    pytest
                        --proxy="username:password@servername:port"
                        --proxy-bypass-list="*.foo.com;github.com"
                    pytest
                        --proxy="servername:port"
                        --proxy-bypass-list="127.0.0.1:8080"
                Default: None.""",
    )
    parser.addoption(
        "--proxy-pac-url",
        "--pac-url",
        action="store",
        dest="proxy_pac_url",
        default=None,
        help="""Designates the proxy PAC URL to use.
                Format: A URL string  OR
                        A username:password@URL string
                Default: None.""",
    )
    parser.addoption(
        "--proxy-driver",
        "--proxy_driver",
        action="store_true",
        dest="proxy_driver",
        default=False,
        help="""If a driver download is needed for tests,
                uses proxy settings set via --proxy=PROXY.""",
    )
    parser.addoption(
        "--multi-proxy",
        "--multi_proxy",
        action="store_true",
        dest="multi_proxy",
        default=False,
        help="""If you need to run multi-threaded tests with
                multiple proxies that require authentication,
                set this to allow multiple configurations.""",
    )
    parser.addoption(
        "--agent",
        "--user-agent",
        "--user_agent",
        action="store",
        dest="user_agent",
        default=None,
        help="""Designates the User-Agent for the browser to use.
                Format: A string.
                Default: None.""",
    )
    parser.addoption(
        "--mobile",
        "--mobile-emulator",
        "--mobile_emulator",
        action="store_true",
        dest="mobile_emulator",
        default=False,
        help="""If this option is enabled, the mobile emulator
                will be used while running tests.""",
    )
    parser.addoption(
        "--metrics",
        "--device-metrics",
        "--device_metrics",
        action="store",
        dest="device_metrics",
        default=None,
        help="""Designates the three device metrics of the mobile
                emulator: CSS Width, CSS Height, and Pixel-Ratio.
                Format: A comma-separated string with the 3 values.
                Examples: "375,734,5" or "411,731,3" or "390,715,3"
                Default: None. (Will use default values if None)""",
    )
    parser.addoption(
        "--chromium_arg",
        "--chromium-arg",
        action="store",
        dest="chromium_arg",
        default=None,
        help="""Add a Chromium argument for Chrome/Edge browsers.
                Format: A comma-separated list of Chromium args.
                If an arg doesn't start with "--", that will be
                added to the beginning of the arg automatically.
                Default: None.""",
    )
    parser.addoption(
        "--firefox_arg",
        "--firefox-arg",
        action="store",
        dest="firefox_arg",
        default=None,
        help="""Add a Firefox argument for Firefox browser runs.
                Format: A comma-separated list of Firefox args.
                If an arg doesn't start with "--", that will be
                added to the beginning of the arg automatically.
                Default: None.""",
    )
    parser.addoption(
        "--firefox_pref",
        "--firefox-pref",
        action="store",
        dest="firefox_pref",
        default=None,
        help="""Set a Firefox preference:value combination.
                Format: A comma-separated list of pref:value items.
                Example usage:
                    --firefox-pref="browser.formfill.enable:True"
                    --firefox-pref="pdfjs.disabled:False"
                    --firefox-pref="abc.def.xyz:42,hello.world:text"
                Boolean and integer values to the right of the ":"
                will be automatically converted into proper format.
                If there's no ":" in the string, then True is used.
                Default: None.""",
    )
    parser.addoption(
        "--extension_zip",
        "--extension-zip",
        "--crx",
        action="store",
        dest="extension_zip",
        default=None,
        help="""Designates the Chrome Extension ZIP file to load.
                Format: A comma-separated list of .zip or .crx files
                containing the Chrome extensions to load.
                Default: None.""",
    )
    parser.addoption(
        "--extension_dir",
        "--extension-dir",
        action="store",
        dest="extension_dir",
        default=None,
        help="""Designates the Chrome Extension folder to load.
                Format: A directory containing the Chrome extension.
                (Can also be a comma-separated list of directories.)
                Default: None.""",
    )
    parser.addoption(
        "--disable_features",
        "--disable-features",
        action="store",
        dest="disable_features",
        default=None,
        help="""Disable Chromium features from Chrome/Edge browsers.
                Format: A comma-separated list of Chromium features.
                Default: None.""",
    )
    parser.addoption(
        "--binary_location",
        "--binary-location",
        "--bl",
        action="store",
        dest="binary_location",
        default=None,
        help="""Sets the path of the Chromium browser binary to use.
                Uses the default location if not os.path.exists(PATH)""",
    )
    parser.addoption(
        "--driver_version",
        "--driver-version",
        action="store",
        dest="driver_version",
        default=None,
        help="""Setting this overrides the default driver version,
                which is set to match the detected browser version.
                Major version only. Example: "--driver-version=114"
                (Only chromedriver and uc_driver are affected.)""",
    )
    parser.addoption(
        "--pls",
        "--page_load_strategy",
        "--page-load-strategy",
        action="store",
        dest="page_load_strategy",
        type=str.lower,
        choices=(
            constants.PageLoadStrategy.NORMAL,
            constants.PageLoadStrategy.EAGER,
            constants.PageLoadStrategy.NONE,
        ),
        default=None,
        help="""This option sets Chrome's pageLoadStrategy.
                List of choices: "normal", "eager", "none".""",
    )
    parser.addoption(
        "--headless",
        action="store_true",
        dest="headless",
        default=False,
        help="""Using this option activates headless mode,
                which is required on headless machines
                UNLESS using a virtual display with Xvfb.
                Default: False on Mac/Windows. True on Linux.""",
    )
    parser.addoption(
        "--headless1",
        action="store_true",
        dest="headless1",
        default=False,
        help="""This option activates the old headless mode,
                which is faster, but has limitations.
                (May be phased out by Chrome in the future.)""",
    )
    parser.addoption(
        "--headless2",
        action="store_true",
        dest="headless2",
        default=False,
        help="""This option activates the new headless mode,
                which supports Chromium extensions, and more,
                but is slower than the standard headless mode.""",
    )
    parser.addoption(
        "--headed",
        "--gui",
        action="store_true",
        dest="headed",
        default=False,
        help="""Using this makes Webdriver run web browsers with
                a GUI when running tests on Linux machines.
                (The default setting on Linux is headless.)
                (The default setting on Mac or Windows is headed.)""",
    )
    parser.addoption(
        "--xvfb",
        action="store_true",
        dest="xvfb",
        default=False,
        help="""Using this makes tests run headlessly using Xvfb
                instead of the browser's built-in headless mode.
                When using "--xvfb", the "--headless" option
                will no longer be enabled by default on Linux.
                Default: False. (Linux-ONLY!)""",
    )
    parser.addoption(
        "--xvfb-metrics",
        "--xvfb_metrics",
        action="store",
        dest="xvfb_metrics",
        default=None,
        help="""Customize the Xvfb metrics (Width,Height) on Linux.
                Format: A comma-separated string with the 2 values.
                Examples: "1920,1080" or "1366,768" or "1024,768".
                Default: None. (None: "1366,768". Min: "1024,768".)""",
    )
    parser.addoption(
        "--locale_code",
        "--locale-code",
        "--locale",
        action="store",
        dest="locale_code",
        default=None,
        help="""Designates the Locale Code for the web browser.
                A Locale is a specific version of a spoken Language.
                The Locale alters visible text on supported websites.
                See: https://seleniumbase.io/help_docs/locale_codes/
                Default: None. (The web browser's default mode.)""",
    )
    parser.addoption(
        "--interval",
        action="store",
        dest="interval",
        default=None,
        help="""This globally overrides the default interval,
                (in seconds), of features that include autoplay
                functionality, such as tours and presentations.
                Overrides from methods take priority over this.
                (Headless Mode skips tours and presentations.)""",
    )
    parser.addoption(
        "--start_page",
        "--start-page",
        "--url",
        action="store",
        dest="start_page",
        default=None,
        help="""Designates the starting URL for the web browser
                when each test begins.
                Default: None.""",
    )
    parser.addoption(
        "--is_pytest",
        "--is-pytest",
        action="store_true",
        dest="is_pytest",
        default=True,
        help="""This is used by the BaseCase class to tell apart
                pytest runs from nosetest runs. (Automatic)""",
    )
    parser.addoption(
        "--all-scripts",
        "--all_scripts",
        action="store_true",
        dest="all_scripts",
        default=False,
        help="""Use this to run `SB()`, `DriverContext()` and
                `Driver()` scripts that are discovered during
                the pytest collection phase.""",
    )
    parser.addoption(
        "--time_limit",
        "--time-limit",
        "--timelimit",
        action="store",
        dest="time_limit",
        default=None,
        help="""Use this to set a time limit per test, in seconds.
                If a test runs beyond the limit, it fails.""",
    )
    parser.addoption(
        "--slow_mode",
        "--slow-mode",
        "--slowmo",
        "--slow",
        action="store_true",
        dest="slow_mode",
        default=False,
        help="""Using this slows down the automation.""",
    )
    parser.addoption(
        "--demo_mode",
        "--demo-mode",
        "--demo",
        action="store_true",
        dest="demo_mode",
        default=False,
        help="""Using this slows down the automation and lets you
                visually see what the tests are actually doing.""",
    )
    parser.addoption(
        "--demo_sleep",
        "--demo-sleep",
        action="store",
        dest="demo_sleep",
        default=None,
        help="""Setting this overrides the Demo Mode sleep
                time that happens after browser actions.""",
    )
    parser.addoption(
        "--highlights",
        action="store",
        dest="highlights",
        default=None,
        help="""Setting this overrides the default number of
                highlight animation loops to have per call.""",
    )
    parser.addoption(
        "--message_duration",
        "--message-duration",
        action="store",
        dest="message_duration",
        default=None,
        help="""Setting this overrides the default time that
                messenger notifications remain visible when
                reaching assert statements during Demo Mode.""",
    )
    parser.addoption(
        "--check_js",
        "--check-js",
        action="store_true",
        dest="js_checking_on",
        default=False,
        help="""The option to check for JavaScript errors after
                every page load.""",
    )
    parser.addoption(
        "--adblock",
        "--ad_block",
        "--ad-block",
        "--block_ads",
        "--block-ads",
        action="store_true",
        dest="ad_block_on",
        default=False,
        help="""Using this makes WebDriver block display ads
                that are defined in ad_block_list.AD_BLOCK_LIST.""",
    )
    parser.addoption(
        "--host_resolver_rules",
        "--host-resolver-rules",
        action="store",
        dest="host_resolver_rules",
        default=None,
        help="""Use this option to set "host-resolver-rules".
                This lets you re-map traffic from any domain.
                Eg. "MAP www.google-analytics.com 0.0.0.0".
                Eg. "MAP * ~NOTFOUND , EXCLUDE myproxy".
                Eg. "MAP * 0.0.0.0 , EXCLUDE 127.0.0.1".
                Eg. "MAP *.google.com myproxy".
                Find more examples on these pages:
                (https://www.electronjs.org/docs/
                 latest/api/command-line-switches)
                (https://www.chromium.org/developers/
                 design-documents/network-stack/socks-proxy/)
                Use comma-separation for multiple host rules.""",
    )
    parser.addoption(
        "--block_images",
        "--block-images",
        action="store_true",
        dest="block_images",
        default=False,
        help="""Using this makes WebDriver block images from
                loading on web pages during tests.""",
    )
    parser.addoption(
        "--do_not_track",
        "--do-not-track",
        action="store_true",
        dest="do_not_track",
        default=False,
        help="""Indicate to websites that you don't want to be
                tracked. The browser will send an extra HTTP
                header each time it requests a web page.
                https://support.google.com/chrome/answer/2790761""",
    )
    parser.addoption(
        "--verify_delay",
        "--verify-delay",
        action="store",
        dest="verify_delay",
        default=None,
        help="""Setting this overrides the default wait time
                before each MasterQA verification pop-up.""",
    )
    parser.addoption(
        "--esc-end",
        "--esc_end",
        "--ee",
        action="store_true",
        dest="esc_end",
        default=False,
        help="""End the current test early via the ESC key.
                The test will be marked as skipped.""",
    )
    parser.addoption(
        "--recorder",
        "--record",
        "--rec",
        "--codegen",
        action="store_true",
        dest="recorder_mode",
        default=False,
        help="""Using this enables the SeleniumBase Recorder,
                which records browser actions for converting
                into SeleniumBase scripts.""",
    )
    parser.addoption(
        "--rec-behave",
        "--rec-gherkin",
        action="store_true",
        dest="rec_behave",
        default=False,
        help="""Not only enables the SeleniumBase Recorder,
                but also saves recorded actions into the
                behave-gerkin format, which includes a
                feature file, an imported steps file,
                and the environment.py file.""",
    )
    parser.addoption(
        "--rec-sleep",
        "--record-sleep",
        action="store_true",
        dest="record_sleep",
        default=False,
        help="""If Recorder Mode is enabled,
                records sleep(seconds) calls.""",
    )
    parser.addoption(
        "--rec-print",
        action="store_true",
        dest="rec_print",
        default=False,
        help="""If Recorder Mode is enabled,
                prints output after tests end.""",
    )
    parser.addoption(
        "--disable_js",
        "--disable-js",
        action="store_true",
        dest="disable_js",
        default=False,
        help="""The option to disable JavaScript on web pages.
                Warning: Most web pages will stop working!""",
    )
    parser.addoption(
        "--disable_cookies",
        "--disable-cookies",
        action="store_true",
        dest="disable_cookies",
        default=False,
        help="""The option to disable Cookies on web pages.
                Warning: Several pages may stop working!""",
    )
    parser.addoption(
        "--disable_csp",
        "--disable-csp",
        "--no_csp",
        "--no-csp",
        "--dcsp",
        action="store_true",
        dest="disable_csp",
        default=False,
        help="""Using this disables the Content Security Policy of
                websites, which may interfere with some features of
                SeleniumBase, such as loading custom JavaScript
                libraries for various testing actions.
                Setting this to True (--disable-csp) overrides the
                value set in seleniumbase/config/settings.py""",
    )
    parser.addoption(
        "--disable_ws",
        "--disable-ws",
        "--dws",
        "--disable-web-security",
        action="store_true",
        dest="disable_ws",
        default=False,
        help="""Using this disables the "Web Security" feature of
                Chrome and Chromium-based browsers such as Edge.""",
    )
    parser.addoption(
        "--enable_ws",
        "--enable-ws",
        "--enable-web-security",
        action="store_true",
        dest="enable_ws",
        default=False,
        help="""Using this enables the "Web Security" feature of
                Chrome and Chromium-based browsers such as Edge.""",
    )
    parser.addoption(
        "--enable_sync",
        "--enable-sync",
        action="store_true",
        dest="enable_sync",
        default=False,
        help="""Using this enables the "Chrome Sync" feature.""",
    )
    parser.addoption(
        "--use_auto_ext",
        "--use-auto-ext",
        "--auto-ext",
        action="store_true",
        dest="use_auto_ext",
        default=False,
        help="""(DEPRECATED) - Enable the automation extension.
                It's not required, but some commands & advanced
                features may need it.""",
    )
    parser.addoption(
        "--undetected",
        "--undetectable",
        "--uc",  # undetected-chromedriver
        action="store_true",
        dest="undetectable",
        default=False,
        help="""Using this option makes chromedriver undetectable
                to websites that use anti-bot services to block
                automation tools from navigating them freely.""",
    )
    parser.addoption(
        "--uc_cdp_events",
        "--uc-cdp-events",
        "--uc-cdp",  # For capturing CDP events during UC Mode
        action="store_true",
        dest="uc_cdp_events",
        default=None,
        help="""Captures CDP events during Undetectable Mode runs.
                Then you can add a listener to perform actions on
                received data, such as printing it to the console:
                    from pprint import pformat
                    self.driver.add_cdp_listener(
                        "*", lambda data: print(pformat(data))
                    )
                    self.open(URL)""",
    )
    parser.addoption(
        "--uc_subprocess",
        "--uc-subprocess",
        "--uc-sub",  # undetected-chromedriver subprocess mode
        action="store_true",
        dest="uc_subprocess",
        default=None,
        help="""(DEPRECATED) - (UC Mode always uses this now.)
                Use undetectable-chromedriver as a subprocess,
                which can help avoid issues that might result.""",
    )
    parser.addoption(
        "--no_sandbox",
        "--no-sandbox",
        action="store_true",
        dest="no_sandbox",
        default=False,
        help="""(DEPRECATED) - "--no-sandbox" is always used now.
                Using this enables the "No Sandbox" feature.
                (This setting is now always enabled by default.)""",
    )
    parser.addoption(
        "--disable_gpu",
        "--disable-gpu",
        action="store_true",
        dest="disable_gpu",
        default=False,
        help="""(DEPRECATED) - GPU is disabled if no swiftshader.
                Using this enables the "Disable GPU" feature.
                (GPU is disabled by default if swiftshader off.)""",
    )
    parser.addoption(
        "--log_cdp",
        "--log-cdp",
        "--log_cdp_events",
        "--log-cdp-events",
        action="store_true",
        dest="log_cdp_events",
        default=None,
        help="""Capture CDP events. Then you can print them.
                Eg. print(driver.get_log("performance"))""",
    )
    parser.addoption(
        "--remote_debug",
        "--remote-debug",
        "--remote-debugger",
        "--remote_debugger",
        action="store_true",
        dest="remote_debug",
        default=False,
        help="""This syncs the browser to Chromium's remote debugger.
                To access the remote debugging interface, go to:
                chrome://inspect/#devices while tests are running.
                The previous URL was at: http://localhost:9222/
                Info: chromedevtools.github.io/devtools-protocol/""",
    )
    parser.addoption(
        "--final-debug",
        "--final-trace",
        "--fdebug",
        "--ftrace",
        action="store_true",
        dest="final_debug",
        default=False,
        help="""Enter Debug Mode at the end of each test.
                To enter Debug Mode only on failures, use "--pdb".
                If using both "--final-debug" and "--pdb" together,
                then Debug Mode will activate twice on failures.""",
    )
    parser.addoption(
        "--dashboard",
        action="store_true",
        dest="dashboard",
        default=False,
        help="""Using this enables the SeleniumBase Dashboard.
                To access the SeleniumBase Dashboard interface,
                open the dashboard.html file located in the same
                folder that the pytest command was run from.""",
    )
    parser.addoption(
        "--dash_title",
        "--dash-title",
        dest="dash_title",
        default=None,
        help="Set the title shown for the generated dashboard.",
    )
    parser.addoption(
        "--enable_3d_apis",
        "--enable-3d-apis",
        action="store_true",
        dest="enable_3d_apis",
        default=False,
        help="""Using this enables WebGL and 3D APIs.""",
    )
    parser.addoption(
        "--swiftshader",
        action="store_true",
        dest="swiftshader",
        default=False,
        help="""Using this enables the "--use-gl=swiftshader"
                feature when running tests on Chrome.""",
    )
    parser.addoption(
        "--incognito",
        "--incognito_mode",
        "--incognito-mode",
        action="store_true",
        dest="incognito",
        default=False,
        help="""Using this enables Chrome's Incognito mode.""",
    )
    parser.addoption(
        "--guest",
        "--guest_mode",
        "--guest-mode",
        action="store_true",
        dest="guest_mode",
        default=False,
        help="""Using this enables Chrome's Guest mode.""",
    )
    parser.addoption(
        "--dark",
        "--dark_mode",
        "--dark-mode",
        action="store_true",
        dest="dark_mode",
        default=False,
        help="""Using this enables Chrome's Dark mode.""",
    )
    parser.addoption(
        "--devtools",
        "--open_devtools",
        "--open-devtools",
        action="store_true",
        dest="devtools",
        default=False,
        help="""Using this opens Chrome's DevTools.""",
    )
    parser.addoption(
        "--rs",
        "--reuse_session",
        "--reuse-session",
        action="store_true",
        dest="reuse_session",
        default=False,
        help="""The option to reuse the selenium browser window
                session for all tests.""",
    )
    parser.addoption(
        "--rcs",
        "--reuse_class_session",
        "--reuse-class-session",
        action="store_true",
        dest="reuse_class_session",
        default=False,
        help="""The option to reuse the selenium browser window
                session for all tests within the same class.""",
    )
    parser.addoption(
        "--crumbs",
        action="store_true",
        dest="crumbs",
        default=False,
        help="""The option to delete all cookies between tests
                that reuse the same browser session. This option
                is only useful if using "--reuse-session"/"--rs"
                or "--reuse-class-session"/"--rcs" because tests
                use a new clean browser if not reusing sessions.""",
    )
    parser.addoption(
        "--disable-beforeunload",
        "--disable_beforeunload",
        action="store_true",
        dest="_disable_beforeunload",
        default=False,
        help="""The option to disable the "beforeunload" event
                on Chromium browsers (Chrome or Edge).
                This is already the default Firefox option.""",
    )
    parser.addoption(
        "--window-position",
        "--window_position",
        action="store",
        dest="window_position",
        default=None,
        help="""The option to set the starting window x,y position
                Format: A comma-separated string with the 2 values.
                Example: "55,66"
                Default: None. (Will use default values if None)""",
    )
    parser.addoption(
        "--window-size",
        "--window_size",
        action="store",
        dest="window_size",
        default=None,
        help="""The option to set the default window "width,height".
                Format: A comma-separated string with the 2 values.
                Example: "1200,800"
                Default: None. (Will use default values if None)""",
    )
    parser.addoption(
        "--maximize_window",
        "--maximize-window",
        "--maximize",
        "--fullscreen",
        action="store_true",
        dest="maximize_option",
        default=False,
        help="""The option to start with a maximized browser window.
                (Overrides the "window-size" option if used.)""",
    )
    parser.addoption(
        "--screenshot",
        "--save_screenshot",
        "--save-screenshot",
        "--ss",
        action="store_true",
        dest="save_screenshot",
        default=False,
        help="""Save a screenshot at the end of every test.
                By default, this is only done for failures.
                Will be saved in the "latest_logs/" folder.""",
    )
    parser.addoption(
        "--no-screenshot",
        "--no_screenshot",
        "--ns",
        action="store_true",
        dest="no_screenshot",
        default=False,
        help="""No screenshots saved unless tests directly ask it.
                This changes default behavior where screenshots are
                saved for test failures and pytest-html reports.""",
    )
    parser.addoption(
        "--visual_baseline",
        "--visual-baseline",
        action="store_true",
        dest="visual_baseline",
        default=False,
        help="""Setting this resets the visual baseline for
                Automated Visual Testing with SeleniumBase.
                When a test calls self.check_window(), it will
                rebuild its files in the visual_baseline folder.""",
    )
    parser.addoption(
        "--wire",
        action="store_true",
        dest="use_wire",
        default=False,
        help="""Use selenium-wire's webdriver for selenium webdriver.""",
    )
    parser.addoption(
        "--external_pdf",
        "--external-pdf",
        action="store_true",
        dest="external_pdf",
        default=False,
        help="""This option sets the following on Chromium:
                "plugins.always_open_pdf_externally": True,
                which causes opened PDF URLs to download immediately,
                instead of being displayed in the browser window.""",
    )
    parser.addoption(
        "--timeout_multiplier",
        "--timeout-multiplier",
        action="store",
        dest="timeout_multiplier",
        default=None,
        help="""Setting this overrides the default timeout
                by the multiplier when waiting for page elements.
                Unused when tests override the default value.""",
    )
    parser.addoption(
        "--lfp",
        "--list-fail-page",
        "--list-fail-pages",
        action="store_true",
        dest="fail_page",
        default=False,
        help="""(For debugging) After each failing test, list the URL
                where the failure occurred.
                Useful when you don't have access to the latest_logs/
                folder, such as when running tests in GitHub Actions.""",
    )

    arg_join = " ".join(sys_argv)
    sb_config._browser_shortcut = None
    sb_config._vd_list = []

    # SeleniumBase does not support pytest-timeout due to hanging browsers.
    for arg in sys_argv:
        if "--timeout=" in arg:
            raise Exception(
                "\n  Don't use --timeout=s from pytest-timeout! "
                "\n  It's not thread-safe for WebDriver processes! "
                "\n  Use --time-limit=s from SeleniumBase instead!\n"
            )

    # Dashboard Mode does not support tests using forked subprocesses.
    if "--forked" in sys_argv and "--dashboard" in sys_argv:
        raise Exception(
            "\n  Dashboard Mode does NOT support forked subprocesses!"
            '\n  (*** DO NOT combine "--forked" with "--dashboard"! ***)\n'
        )

    # Reuse-Session Mode does not support tests using forked subprocesses.
    if "--forked" in sys_argv and (
        "--rs" in sys_argv or "--reuse-session" in sys_argv
    ):
        raise Exception(
            "\n  Reuse-Session Mode does NOT support forked subprocesses!"
            '\n  (DO NOT combine "--forked" with "--rs"/"--reuse-session"!)\n'
        )

    # Recorder Mode does not support multi-threaded / multi-process runs.
    if (
        "--recorder" in sys_argv
        or "--record" in sys_argv
        or "--rec" in sys_argv
    ):
        if ("-n" in sys_argv) or (" -n=" in arg_join) or ("-c" in sys_argv):
            raise Exception(
                "\n  Recorder Mode does NOT support multi-process mode (-n)!"
                '\n  (DO NOT combine "--recorder" with "-n NUM_PROCESSES"!)\n'
            )

    using_recorder = False
    if (
        "--recorder" in sys_argv
        or "--record" in sys_argv
        or "--rec" in sys_argv
    ):
        using_recorder = True

    # As a shortcut, you can use "--edge" instead of "--browser=edge", etc,
    # but you can only specify one default browser for tests. (Default: chrome)
    browser_changes = 0
    browser_set = None
    browser_text = None
    browser_list = []
    if "--browser=chrome" in sys_argv or "--browser chrome" in sys_argv:
        browser_changes += 1
        browser_set = "chrome"
        browser_list.append("--browser=chrome")
    if "--browser=edge" in sys_argv or "--browser edge" in sys_argv:
        browser_changes += 1
        browser_set = "edge"
        browser_list.append("--browser=edge")
    if "--browser=firefox" in sys_argv or "--browser firefox" in sys_argv:
        browser_changes += 1
        browser_set = "firefox"
        browser_list.append("--browser=firefox")
    if "--browser=safari" in sys_argv or "--browser safari" in sys_argv:
        browser_changes += 1
        browser_set = "safari"
        browser_list.append("--browser=safari")
    if "--browser=ie" in sys_argv or "--browser ie" in sys_argv:
        browser_changes += 1
        browser_set = "ie"
        browser_list.append("--browser=ie")
    if "--browser=remote" in sys_argv or "--browser remote" in sys_argv:
        browser_changes += 1
        browser_set = "remote"
        browser_list.append("--browser=remote")
    browser_text = browser_set
    if "--chrome" in sys_argv and not browser_set == "chrome":
        browser_changes += 1
        browser_text = "chrome"
        sb_config._browser_shortcut = "chrome"
        browser_list.append("--chrome")
    if "--edge" in sys_argv and not browser_set == "edge":
        browser_changes += 1
        browser_text = "edge"
        sb_config._browser_shortcut = "edge"
        browser_list.append("--edge")
    if "--firefox" in sys_argv and not browser_set == "firefox":
        browser_changes += 1
        browser_text = "firefox"
        sb_config._browser_shortcut = "firefox"
        browser_list.append("--firefox")
    if "--ie" in sys_argv and not browser_set == "ie":
        browser_changes += 1
        browser_text = "ie"
        sb_config._browser_shortcut = "ie"
        browser_list.append("--ie")
    if "--safari" in sys_argv and not browser_set == "safari":
        browser_changes += 1
        browser_text = "safari"
        sb_config._browser_shortcut = "safari"
        browser_list.append("--safari")
    if browser_changes > 1:
        message = "\n  TOO MANY browser types were entered!"
        message += "\n  There were %s found:\n  >  %s" % (
            browser_changes,
            ", ".join(browser_list),
        )
        message += "\n  ONLY ONE default browser is allowed!"
        message += "\n  Select a single browser & try again!\n"
        raise Exception(message)
    if (
        using_recorder
        and browser_changes == 1
        and browser_text not in ["chrome", "edge"]
    ):
        message = (
            "\n  Recorder Mode ONLY supports Chrome and Edge!"
            '\n  (Your browser choice was: "%s")\n' % browser_list[0]
        )
        raise Exception(message)
    undetectable = False
    if (
        "--undetected" in sys_argv
        or "--undetectable" in sys_argv
        or "--uc" in sys_argv
        or "--uc-cdp-events" in sys_argv
        or "--uc_cdp_events" in sys_argv
        or "--uc-cdp" in sys_argv
        or "--uc-subprocess" in sys_argv
        or "--uc_subprocess" in sys_argv
        or "--uc-sub" in sys_argv
    ):
        undetectable = True
    if (
        browser_changes == 1
        and browser_text not in ["chrome"]
        and undetectable
    ):
        message = (
            '\n  Undetected-Chromedriver Mode ONLY supports Chrome!'
            '\n  ("--uc" / "--undetected" / "--undetectable")'
            '\n  (Your browser choice was: "%s")\n' % browser_list[0]
        )
        raise Exception(message)
    if undetectable and "--wire" in sys_argv:
        raise Exception(
            "\n  SeleniumBase doesn't support mixing --uc with --wire mode!"
            "\n  If you need both, override get_new_driver() from BaseCase:"
            "\n  https://seleniumbase.io/help_docs/syntax_formats/#sb_sf_09\n"
        )


def pytest_configure(config):
    """This runs after command-line options have been parsed."""
    sb_config.item_count = 0
    sb_config.item_count_passed = 0
    sb_config.item_count_failed = 0
    sb_config.item_count_skipped = 0
    sb_config.item_count_untested = 0
    sb_config.is_pytest = True
    sb_config.is_behave = False
    sb_config.is_nosetest = False
    sb_config.is_context_manager = False
    sb_config.pytest_config = config
    sb_config.browser = config.getoption("browser")
    if sb_config._browser_shortcut:
        sb_config.browser = sb_config._browser_shortcut
    sb_config.account = config.getoption("account")
    sb_config.data = config.getoption("data")
    sb_config.var1 = config.getoption("var1")
    sb_config.var2 = config.getoption("var2")
    sb_config.var3 = config.getoption("var3")
    sb_config.variables = config.getoption("variables")
    sb_config.environment = config.getoption("environment")
    sb_config.with_selenium = config.getoption("with_selenium")
    sb_config.user_agent = config.getoption("user_agent")
    sb_config.mobile_emulator = config.getoption("mobile_emulator")
    sb_config.device_metrics = config.getoption("device_metrics")
    sb_config.headless = config.getoption("headless")
    sb_config.headless1 = config.getoption("headless1")
    if sb_config.headless1:
        sb_config.headless = True
    sb_config.headless2 = config.getoption("headless2")
    if sb_config.headless2 and sb_config.browser == "firefox":
        sb_config.headless2 = False  # Only for Chromium browsers
        sb_config.headless = True  # Firefox has regular headless
    elif sb_config.browser not in ["chrome", "edge"]:
        sb_config.headless2 = False  # Only for Chromium browsers
    sb_config.headed = config.getoption("headed")
    sb_config.xvfb = config.getoption("xvfb")
    sb_config.xvfb_metrics = config.getoption("xvfb_metrics")
    sb_config.locale_code = config.getoption("locale_code")
    sb_config.interval = config.getoption("interval")
    sb_config.start_page = config.getoption("start_page")
    sb_config.chromium_arg = config.getoption("chromium_arg")
    sb_config.firefox_arg = config.getoption("firefox_arg")
    sb_config.firefox_pref = config.getoption("firefox_pref")
    sb_config.extension_zip = config.getoption("extension_zip")
    sb_config.extension_dir = config.getoption("extension_dir")
    sb_config.disable_features = config.getoption("disable_features")
    sb_config.binary_location = config.getoption("binary_location")
    if config.getoption("use_cft") and not sb_config.binary_location:
        sb_config.binary_location = "cft"
    elif config.getoption("use_chs") and not sb_config.binary_location:
        sb_config.binary_location = "chs"
    if (
        sb_config.binary_location
        and sb_config.binary_location.lower() == "chs"
        and sb_config.browser == "chrome"
    ):
        sb_config.headless = True
        sb_config.headless1 = False
        sb_config.headless2 = False
    sb_config.driver_version = config.getoption("driver_version")
    sb_config.page_load_strategy = config.getoption("page_load_strategy")
    sb_config.with_testing_base = config.getoption("with_testing_base")
    sb_config.with_db_reporting = config.getoption("with_db_reporting")
    sb_config.with_s3_logging = config.getoption("with_s3_logging")
    sb_config.with_screen_shots = config.getoption("with_screen_shots")
    sb_config.with_basic_test_info = config.getoption("with_basic_test_info")
    sb_config.with_page_source = config.getoption("with_page_source")
    sb_config.protocol = config.getoption("protocol")
    sb_config.servername = config.getoption("servername")
    sb_config.port = config.getoption("port")
    if sb_config.servername != "localhost":
        # Using Selenium Grid
        # (Set --server="127.0.0.1" for localhost Grid)
        if str(sb_config.port) == "443":
            sb_config.protocol = "https"
    sb_config.proxy_string = config.getoption("proxy_string")
    sb_config.proxy_bypass_list = config.getoption("proxy_bypass_list")
    sb_config.proxy_pac_url = config.getoption("proxy_pac_url")
    sb_config.proxy_driver = config.getoption("proxy_driver")
    sb_config.multi_proxy = config.getoption("multi_proxy")
    sb_config.cap_file = config.getoption("cap_file")
    sb_config.cap_string = config.getoption("cap_string")
    sb_config.settings_file = config.getoption("settings_file")
    sb_config.user_data_dir = config.getoption("user_data_dir")
    sb_config.database_env = config.getoption("database_env")
    sb_config.log_path = constants.Logs.LATEST + "/"
    sb_config.archive_logs = config.getoption("archive_logs")
    if config.getoption("archive_downloads"):
        settings.ARCHIVE_EXISTING_DOWNLOADS = True
    if config.getoption("skip_js_waits"):
        settings.SKIP_JS_WAITS = True
    if config.getoption("wait_for_angularjs"):
        settings.WAIT_FOR_ANGULARJS = True
    sb_config.all_scripts = config.getoption("all_scripts")
    sb_config._time_limit = config.getoption("time_limit")
    sb_config.time_limit = config.getoption("time_limit")
    sb_config.slow_mode = config.getoption("slow_mode")
    sb_config.demo_mode = config.getoption("demo_mode")
    sb_config.demo_sleep = config.getoption("demo_sleep")
    sb_config.highlights = config.getoption("highlights")
    sb_config.message_duration = config.getoption("message_duration")
    sb_config.js_checking_on = config.getoption("js_checking_on")
    sb_config.ad_block_on = config.getoption("ad_block_on")
    sb_config.host_resolver_rules = config.getoption("host_resolver_rules")
    sb_config.block_images = config.getoption("block_images")
    sb_config.do_not_track = config.getoption("do_not_track")
    sb_config.verify_delay = config.getoption("verify_delay")
    sb_config.esc_end = config.getoption("esc_end")
    sb_config.recorder_mode = config.getoption("recorder_mode")
    sb_config.recorder_ext = config.getoption("recorder_mode")  # Again
    sb_config.rec_behave = config.getoption("rec_behave")
    sb_config.rec_print = config.getoption("rec_print")
    sb_config.record_sleep = config.getoption("record_sleep")
    if sb_config.rec_print and not sb_config.recorder_mode:
        sb_config.recorder_mode = True
        sb_config.recorder_ext = True
    elif sb_config.rec_behave and not sb_config.recorder_mode:
        sb_config.recorder_mode = True
        sb_config.recorder_ext = True
    elif sb_config.record_sleep and not sb_config.recorder_mode:
        sb_config.recorder_mode = True
        sb_config.recorder_ext = True
    sb_config.disable_cookies = config.getoption("disable_cookies")
    sb_config.disable_js = config.getoption("disable_js")
    sb_config.disable_csp = config.getoption("disable_csp")
    sb_config.disable_ws = config.getoption("disable_ws")
    sb_config.enable_ws = config.getoption("enable_ws")
    if not sb_config.disable_ws:
        sb_config.enable_ws = True
    sb_config.enable_sync = config.getoption("enable_sync")
    sb_config.use_auto_ext = config.getoption("use_auto_ext")
    sb_config.undetectable = config.getoption("undetectable")
    sb_config.uc_cdp_events = config.getoption("uc_cdp_events")
    if sb_config.uc_cdp_events and not sb_config.undetectable:
        sb_config.undetectable = True
    sb_config.uc_subprocess = config.getoption("uc_subprocess")
    if sb_config.uc_subprocess and not sb_config.undetectable:
        sb_config.undetectable = True
    sb_config.no_sandbox = config.getoption("no_sandbox")
    sb_config.disable_gpu = config.getoption("disable_gpu")
    sb_config.log_cdp_events = config.getoption("log_cdp_events")
    sb_config.remote_debug = config.getoption("remote_debug")
    sb_config.final_debug = config.getoption("final_debug")
    sb_config.dashboard = config.getoption("dashboard")
    sb_config.dash_title = config.getoption("dash_title")
    sb_config.enable_3d_apis = config.getoption("enable_3d_apis")
    sb_config.swiftshader = config.getoption("swiftshader")
    sb_config.incognito = config.getoption("incognito")
    sb_config.guest_mode = config.getoption("guest_mode")
    sb_config.dark_mode = config.getoption("dark_mode")
    sb_config.devtools = config.getoption("devtools")
    sb_config.reuse_session = config.getoption("reuse_session")
    sb_config.reuse_class_session = config.getoption("reuse_class_session")
    if sb_config.reuse_class_session:
        sb_config.reuse_session = True
    sb_config.shared_driver = None  # The default driver for session reuse
    sb_config.crumbs = config.getoption("crumbs")
    sb_config._disable_beforeunload = config.getoption("_disable_beforeunload")
    sb_config.window_position = config.getoption("window_position")
    sb_config.window_size = config.getoption("window_size")
    sb_config.maximize_option = config.getoption("maximize_option")
    sb_config.save_screenshot = config.getoption("save_screenshot")
    sb_config.no_screenshot = config.getoption("no_screenshot")
    sb_config.visual_baseline = config.getoption("visual_baseline")
    sb_config.use_wire = config.getoption("use_wire")
    sb_config.external_pdf = config.getoption("external_pdf")
    sb_config.timeout_multiplier = config.getoption("timeout_multiplier")
    sb_config.list_fp = config.getoption("fail_page")
    sb_config._is_timeout_changed = False
    sb_config._has_logs = False
    sb_config._fail_page = None
    sb_config._SMALL_TIMEOUT = settings.SMALL_TIMEOUT
    sb_config._LARGE_TIMEOUT = settings.LARGE_TIMEOUT
    sb_config.pytest_html_report = config.getoption("htmlpath")  # --html=FILE
    sb_config._sb_class = None  # (Used with the sb fixture for "--rcs")
    sb_config._sb_node = {}  # sb node dictionary (Used with the sb fixture)
    # Dashboard-specific variables
    sb_config._results = {}  # SBase Dashboard test results
    sb_config._duration = {}  # SBase Dashboard test duration
    sb_config._display_id = {}  # SBase Dashboard display ID
    sb_config._d_t_log_path = {}  # SBase Dashboard test log path
    sb_config._dash_html = None  # SBase Dashboard HTML copy
    sb_config._test_id = None  # SBase Dashboard test id
    sb_config._latest_display_id = None  # The latest SBase display id
    sb_config._dashboard_initialized = False  # Becomes True after init
    sb_config._has_exception = False  # This becomes True if any test fails
    sb_config._multithreaded = False  # This becomes True if multithreading
    sb_config._only_unittest = True  # If any test uses BaseCase, becomes False
    sb_config._sbase_detected = False  # Becomes True during SeleniumBase tests
    sb_config._extra_dash_entries = []  # Dashboard entries for non-SBase tests
    sb_config._using_html_report = False  # Becomes True when using html report
    sb_config._dash_is_html_report = False  # Dashboard becomes the html report
    sb_config._saved_dashboard_pie = None  # Copy of pie chart for html report
    sb_config._dash_final_summary = None  # Dash status to add to html report
    sb_config._html_report_name = None  # The name of the pytest html report
    sb_config._html_report_copy = None  # The copy of the pytest html report

    arg_join = " ".join(sys_argv)
    if (
        "-n" in sys_argv
        or " -n=" in arg_join
        or " -n" in arg_join
        or "-c" in sys_argv
        or (
            "addopts" in config.inicfg.keys()
            and (
                "-n=" in config.inicfg["addopts"]
                or "-n " in config.inicfg["addopts"]
                or "-n" in config.inicfg["addopts"]
            )
        )
    ):
        sb_config._multithreaded = True
    if (
        "--html" in sys_argv
        or " --html=" in arg_join
        or (
            "addopts" in config.inicfg.keys()
            and (
                "--html=" in config.inicfg["addopts"]
                or "--html " in config.inicfg["addopts"]
            )
        )
    ):
        sb_config._using_html_report = True
        sb_config._html_report_name = config.getoption("htmlpath")
        if sb_config.dashboard:
            if sb_config._html_report_name == "dashboard.html":
                sb_config._dash_is_html_report = True
        sb_config._html_report_copy = "last_report.html"

    # Recorder Mode does not support multi-threaded / multi-process runs.
    if sb_config.recorder_mode and sb_config._multithreaded:
        # At this point, the user likely put a "-n NUM" in the pytest.ini file.
        # Since raising an exception in pytest_configure raises INTERNALERROR,
        # print a message here instead and cancel Recorder Mode.
        print(
            "\n  Recorder Mode does NOT support multi-process mode (-n)!"
            '\n  (DO NOT combine "--recorder" with "-n NUM_PROCESSES"!)'
            "\n  (The Recorder WILL BE DISABLED during this run!)\n"
        )
        sb_config.recorder_mode = False
        sb_config.recorder_ext = False

    if sb_config.xvfb and "linux" not in sys.platform:
        # The Xvfb virtual display server is for Linux OS Only!
        sb_config.xvfb = False
    if (
        "linux" in sys.platform
        and not sb_config.headed
        and not sb_config.headless
        and not sb_config.headless2
        and not sb_config.xvfb
    ):
        if not sb_config.undetectable:
            print(
                "(Linux uses --headless by default. "
                "To override, use --headed / --gui. "
                "For Xvfb mode instead, use --xvfb. "
                "Or you can hide this info by using "
                "--headless / --headless2 / --uc.)"
            )
            sb_config.headless = True
        else:
            sb_config.xvfb = True

    # Recorder Mode can still optimize scripts in --headless2 mode.
    if sb_config.recorder_mode and sb_config.headless:
        sb_config.headless = False
        sb_config.headless1 = False
        sb_config.headless2 = True

    if not sb_config.headless and not sb_config.headless2:
        sb_config.headed = True

    if config.getoption("use_chrome"):
        sb_config.browser = "chrome"
    elif config.getoption("use_edge"):
        sb_config.browser = "edge"
    elif config.getoption("use_firefox"):
        sb_config.browser = "firefox"
    elif config.getoption("use_ie"):
        sb_config.browser = "ie"
    elif config.getoption("use_safari"):
        sb_config.browser = "safari"
    else:
        pass  # Use the browser specified by "--browser=BROWSER"

    if sb_config.browser == "safari" and sb_config.headless:
        sb_config.headless = False  # Safari doesn't support headless mode
        sb_config.headless1 = False

    if sb_config.dash_title:
        constants.Dashboard.TITLE = sb_config.dash_title.replace("_", " ")

    if sb_config.save_screenshot and sb_config.no_screenshot:
        sb_config.save_screenshot = False  # "no_screenshot" has priority

    if (
        "-v" in sys_argv and not sb_config._multithreaded
        or (
            hasattr(config, "invocation_params")
            and "-v" in config.invocation_params.args
            and (
                "-n=1" in config.invocation_params.args
                or "-n1" in config.invocation_params.args
                or "-n 1" in " ".join(config.invocation_params.args)
            )
        )
    ):
        sb_config.list_fp = True  # List the fail pages in console output

    if (
        sb_config._multithreaded
        and "--co" not in sys_argv
        and "--collect-only" not in sys_argv
    ):
        from seleniumbase.core import download_helper
        from seleniumbase.core import proxy_helper

        log_helper.log_folder_setup(
            constants.Logs.LATEST + "/", sb_config.archive_logs
        )
        download_helper.reset_downloads_folder()
        proxy_helper.remove_proxy_zip_if_present()


def pytest_sessionstart(session):
    pass


def _get_test_ids_(the_item):
    test_id = the_item.nodeid
    if not test_id:
        test_id = "unidentified_TestCase"
    display_id = test_id
    r"""
    # Now using the nodeid for both the test_id and display_id.
    # (This only impacts tests using The Dashboard.)
    # If there are any issues, we'll revert back to the old code.
    test_id = the_item.nodeid.split("/")[-1].replace(" ", "_")
    if "[" in test_id:
        test_id_intro = test_id.split("[")[0]
        parameter = test_id.split("[")[1]
        parameter = re.sub(re.compile(r"\W"), "", parameter)
        test_id = test_id_intro + "__" + parameter
    display_id = test_id
    test_id = test_id.replace("/", ".").replace("\\", ".")
    test_id = test_id.replace("::", ".").replace(".py", "")
    """
    return test_id, display_id


def _create_dashboard_assets_():
    import codecs
    from seleniumbase.js_code.live_js import live_js
    from seleniumbase.core.style_sheet import get_pytest_style

    abs_path = os.path.abspath(".")
    assets_folder = os.path.join(abs_path, "assets")
    if not os.path.exists(assets_folder):
        with suppress(Exception):
            os.makedirs(assets_folder, exist_ok=True)
    pytest_style_css = os.path.join(assets_folder, "pytest_style.css")
    add_pytest_style_css = True
    if os.path.exists(pytest_style_css):
        existing_pytest_style = None
        with open(pytest_style_css, "r") as f:
            existing_pytest_style = f.read()
        if existing_pytest_style == get_pytest_style():
            add_pytest_style_css = False
    if add_pytest_style_css:
        out_file = codecs.open(pytest_style_css, "w+", encoding="utf-8")
        out_file.writelines(get_pytest_style())
        out_file.close()
    live_js_file = os.path.join(assets_folder, "live.js")
    add_live_js_file = True
    if os.path.exists(live_js_file):
        existing_live_js = None
        with open(live_js_file, "r") as f:
            existing_live_js = f.read()
        if existing_live_js == live_js:
            add_live_js_file = False
    if add_live_js_file:
        out_file = codecs.open(live_js_file, "w+", encoding="utf-8")
        out_file.writelines(live_js)
        out_file.close()


def pytest_itemcollected(item):
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    sb_config.item_count += 1
    if sb_config.dashboard:
        test_id, display_id = _get_test_ids_(item)
        sb_config._results[test_id] = "Untested"
        sb_config._duration[test_id] = "-"
        sb_config._display_id[test_id] = display_id
        sb_config._d_t_log_path[test_id] = None


def pytest_deselected(items):
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    if sb_config.dashboard:
        sb_config.item_count -= len(items)
        for item in items:
            test_id, display_id = _get_test_ids_(item)
            if test_id in sb_config._results.keys():
                sb_config._results.pop(test_id)


def pytest_collection_finish(session):
    """This runs after item collection is finalized.
    https://docs.pytest.org/en/stable/reference.html """
    sb_config._context_of_runner = False  # Context Manager Compatibility
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    if len(session.items) > 0 and not sb_config._multithreaded:
        from seleniumbase.core import download_helper
        from seleniumbase.core import proxy_helper

        log_helper.log_folder_setup(
            constants.Logs.LATEST + "/", sb_config.archive_logs
        )
        download_helper.reset_downloads_folder()
        proxy_helper.remove_proxy_zip_if_present()
    if sb_config.dashboard and len(session.items) > 0:
        _create_dashboard_assets_()
        # Print the Dashboard path if at least one test runs.
        sb_config.item_count_untested = sb_config.item_count
        dash_path = os.path.join(os.getcwd(), "dashboard.html")
        dash_url = "file://" + dash_path.replace("\\", "/")
        star_len = len("Dashboard: ") + len(dash_url)
        with suppress(Exception):
            terminal_size = os.get_terminal_size().columns
            if terminal_size > 30 and star_len > terminal_size:
                star_len = terminal_size
        stars = "*" * star_len
        c1 = ""
        cr = ""
        if "linux" not in sys.platform:
            c1 = colorama.Fore.BLUE + colorama.Back.LIGHTCYAN_EX
            cr = colorama.Style.RESET_ALL
        if sb_config._multithreaded:
            if (
                hasattr(session.config, "workerinput")
                and session.config.workerinput["workerid"] == "gw0"
            ):
                sys.stderr.write(
                    "\nDashboard: %s%s%s\n%s\n" % (c1, dash_url, cr, stars)
                )
        else:
            print("Dashboard: %s%s%s\n%s" % (c1, dash_url, cr, stars))


def pytest_runtest_setup(item):
    """This runs before every test with pytest."""
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    if sb_config.dashboard:
        sb_config._sbase_detected = False
        sb_config._pdb_failure = False
    sb_config._fail_page = None
    test_id, display_id = _get_test_ids_(item)
    sb_config._test_id = test_id
    sb_config._latest_display_id = display_id


def pytest_runtest_teardown(item):
    """This runs after every test with pytest.
    Make sure that webdriver and headless displays have exited.
    (Has zero effect on tests using --reuse-session / --rs)"""
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    with suppress(Exception):
        if hasattr(item, "_testcase") or hasattr(sb_config, "_sb_pdb_driver"):
            if hasattr(item, "_testcase"):
                self = item._testcase
                with suppress(Exception):
                    if (
                        hasattr(self, "driver")
                        and self.driver
                        and "--pdb" not in sys_argv
                    ):
                        if not (is_windows or self.driver.service.process):
                            self.driver.quit()
            elif (
                hasattr(sb_config, "_sb_pdb_driver")
                and sb_config._sb_pdb_driver
            ):
                with suppress(Exception):
                    if (
                        not is_windows
                        or sb_config._sb_pdb_driver.service.process
                    ):
                        sb_config._sb_pdb_driver.quit()
                        sb_config._sb_pdb_driver = None
        with suppress(Exception):
            if (
                hasattr(self, "_xvfb_display")
                and self._xvfb_display
                and hasattr(self._xvfb_display, "stop")
                and (
                    not hasattr(sb_config, "reuse_session")
                    or (
                        hasattr(sb_config, "reuse_session")
                        and not sb_config.reuse_session
                    )
                )
            ):
                self.headless_active = False
                sb_config.headless_active = False
                self._xvfb_display.stop()
                self._xvfb_display = None
            if (
                hasattr(sb_config, "_virtual_display")
                and sb_config._virtual_display
                and hasattr(sb_config._virtual_display, "stop")
                and (
                    not hasattr(sb_config, "reuse_session")
                    or (
                        hasattr(sb_config, "reuse_session")
                        and not sb_config.reuse_session
                    )
                )
            ):
                sb_config._virtual_display.stop()
                sb_config._virtual_display = None
    if (
        (
            sb_config._has_exception
            or (python3_11_or_newer and py311_patch2)
            or "--pdb" in sys_argv
        )
        and sb_config.list_fp
        and sb_config._fail_page
    ):
        if (
            "-s" in sys_argv
            or "--capture=no" in sys_argv
            or "--capture=tee-sys" in sys_argv
            or (
                hasattr(sb_config.pytest_config, "invocation_params")
                and (
                    "-s" in sb_config.pytest_config.invocation_params.args
                    or "--capture=no" in (
                        sb_config.pytest_config.invocation_params.args
                    )
                    or "--capture=tee-sys" in (
                        sb_config.pytest_config.invocation_params.args
                    )
                )
            )
        ):
            print("\n=> Fail Page: %s" % sb_config._fail_page)
        else:
            sys.stdout.write("\n=> Fail Page: %s\n" % sb_config._fail_page)


def pytest_html_duration_format(duration):
    return "%.2f" % duration


def pytest_sessionfinish(session):
    pass


def pytest_terminal_summary(terminalreporter):
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    if (
        not hasattr(terminalreporter, "stats")
        or not hasattr(terminalreporter.stats, "keys")
    ):
        return
    if len(terminalreporter.stats.keys()) == 0:
        return
    if not sb_config._multithreaded and not sb_config._sbase_detected:
        return
    latest_logs_dir = os.path.join(os.getcwd(), constants.Logs.LATEST) + os.sep
    if (
        "failed" in terminalreporter.stats.keys()
        and os.path.exists(latest_logs_dir)
        and os.listdir(latest_logs_dir)
    ):
        sb_config._has_exception = True
    if sb_config._multithreaded:
        if os.path.exists(latest_logs_dir) and os.listdir(latest_logs_dir):
            sb_config._has_exception = True
        if sb_config.dashboard:
            abs_path = os.path.abspath(".")
            dash_lock = constants.Dashboard.LOCKFILE
            dash_lock_path = os.path.join(abs_path, dash_lock)
            if os.path.exists(dash_lock_path):
                sb_config._only_unittest = False
    if sb_config._has_exception and (
        sb_config.dashboard and not sb_config._only_unittest
    ):
        # Print link a second time because the first one may be off-screen
        dashboard_file = os.path.join(os.getcwd(), "dashboard.html")
        dashboard_url = "file://" + dashboard_file.replace("\\", "/")
        terminalreporter.write_sep("-", "Dashboard = %s" % dashboard_url)
    if (
        sb_config._has_exception
        or sb_config.save_screenshot
        or sb_config._has_logs
    ):
        # Log files are generated during test failures and Screenshot Mode
        terminalreporter.write_sep(
            "-", "Latest Logs dir: %s" % latest_logs_dir
        )


def _perform_pytest_unconfigure_(config):
    from seleniumbase.core import proxy_helper

    reporter = config.pluginmanager.get_plugin("terminalreporter")
    duration = time.time() - reporter._sessionstarttime
    if (
        (hasattr(sb_config, "multi_proxy") and not sb_config.multi_proxy)
        or not hasattr(sb_config, "multi_proxy")
    ):
        proxy_helper.remove_proxy_zip_if_present()
    if hasattr(sb_config, "reuse_session") and sb_config.reuse_session:
        # Close the shared browser session
        if sb_config.shared_driver:
            try:
                if (
                    not is_windows
                    or sb_config.browser == "ie"
                    or sb_config.shared_driver.service.process
                ):
                    sb_config.shared_driver.quit()
            except AttributeError:
                pass
            except Exception:
                pass
        sb_config.shared_driver = None
        with suppress(Exception):
            if (
                hasattr(sb_config, "_virtual_display")
                and sb_config._virtual_display
                and hasattr(sb_config._virtual_display, "stop")
            ):
                sb_config._virtual_display.stop()
                sb_config._virtual_display = None
                sb_config.headless_active = False
            if hasattr(sb_config, "_vd_list") and sb_config._vd_list:
                if isinstance(sb_config._vd_list, list):
                    for display in sb_config._vd_list:
                        if display:
                            with suppress(Exception):
                                display.stop()
    if hasattr(sb_config, "log_path") and sb_config.item_count > 0:
        log_helper.archive_logs_if_set(
            constants.Logs.LATEST + "/", sb_config.archive_logs
        )
        if os.path.exists("./assets/"):  # Used by pytest-html reports
            with suppress(Exception):
                shared_utils.make_dir_files_writable("./assets/")
    log_helper.clear_empty_logs()
    # Dashboard post-processing: Disable time-based refresh and stamp complete
    if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
        html_report_path = None
        the_html_r = None
        abs_path = os.path.abspath(".")
        if sb_config._html_report_name:
            html_report_path = os.path.join(
                abs_path, sb_config._html_report_name
            )
        if sb_config._html_report_copy:
            html_report_path_copy = os.path.join(
                abs_path, sb_config._html_report_copy
            )
        if (
            sb_config._using_html_report
            and html_report_path
            and os.path.exists(html_report_path)
        ):
            with open(html_report_path, "r", encoding="utf-8") as f:
                the_html_r = f.read()
            assets_chunk = "if (assets.length === 1) {"
            remove_media = "container.classList.remove('media-container')"
            rm_n_left = '<div class="media-container__nav--left"><</div>'
            rm_n_right = '<div class="media-container__nav--right">></div>'
            the_html_r = the_html_r.replace(
                assets_chunk,
                "%s %s" % (assets_chunk, remove_media),
            )
            the_html_r = the_html_r.replace(rm_n_left, "")
            the_html_r = the_html_r.replace(rm_n_right, "")
            the_html_r = the_html_r.replace("<ul>$", "$")
            the_html_r = the_html_r.replace("}<ul>", "}")
            the_html_r = the_html_r.replace(
                "<li>${val}</li>", "${val},&nbsp;&nbsp;"
            )
            the_html_r = the_html_r.replace(
                "<div>${value}</div>", "<span>${value}</span"
            )
            ph_link = '<a href="https://pypi.python.org/pypi/pytest-html">'
            sb_link = (
                '<a href="https://github.com/seleniumbase/SeleniumBase">'
                'SeleniumBase</a>'
            )
            the_html_r = the_html_r.replace(
                ph_link, "%s and %s" % (sb_link, ph_link)
            )
            the_html_r = the_html_r.replace(
                "findAll('.collapsible", "//findAll('.collapsible"
            )
            the_html_r = the_html_r.replace(
                "mediaName.innerText", "//mediaName.innerText"
            )
            the_html_r = the_html_r.replace(
                "counter.innerText", "//counter.innerText"
            )
            run_count = '<p class="run-count">'
            run_c_loc = the_html_r.find(run_count)
            rc_loc = the_html_r.find(" took ", run_c_loc)
            end_rc_loc = the_html_r.find(".</p>", rc_loc)
            run_time = "%.2f" % duration
            new_time = " ran in %s seconds" % run_time
            the_html_r = (
                the_html_r[:rc_loc] + new_time + the_html_r[end_rc_loc:]
            )
            with open(html_report_path, "w", encoding="utf-8") as f:
                f.write(the_html_r)  # Finalize the HTML report
            with suppress(Exception):
                shared_utils.make_writable(html_report_path)
            with open(html_report_path_copy, "w", encoding="utf-8") as f:
                f.write(the_html_r)  # Finalize the HTML report copy
            with suppress(Exception):
                shared_utils.make_writable(html_report_path_copy)
            assets_style = "./assets/style.css"
            if os.path.exists(assets_style):
                html_style = None
                with open(assets_style, "r", encoding="utf-8") as f:
                    html_style = f.read()
                if html_style:
                    html_style = html_style.replace("top: -50px;", "top: 2px;")
                    html_style = html_style.replace("+ 50px)", "+ 40px)")
                    html_style = html_style.replace("ht: 240px;", "ht: 228px;")
                    html_style = html_style.replace(
                        "- 80px);", "- 80px);\n  margin-bottom: -42px;"
                    )
                    html_style = html_style.replace(".collapsible", ".oldc")
                    html_style = html_style.replace(" (hide details)", "")
                    html_style = html_style.replace(" (show details)", "")
                with open(assets_style, "w", encoding="utf-8") as f:
                    f.write(html_style)
                with suppress(Exception):
                    shared_utils.make_writable(assets_style)
        # Done with "pytest_unconfigure" unless using the Dashboard
        return
    stamp = ""
    if sb_config._dash_is_html_report:
        # (If the Dashboard URL is the same as the HTML Report URL:)
        # Have the html report refresh back to a dashboard on update
        stamp += (
            '\n<script type="text/javascript" src="%s">'
            "</script>" % constants.Dashboard.LIVE_JS
        )
    stamp += "\n<!--Test Run Complete-->"
    find_it = constants.Dashboard.META_REFRESH_HTML
    swap_with = ""  # Stop refreshing the page after the run is done
    find_it_2 = "Awaiting results... (Refresh the page for updates)"
    swap_with_2 = (
        "Test Run ENDED: Some results UNREPORTED due to skipped tearDown()"
    )
    find_it_3 = '<td class="col-result">Untested</td>'
    swap_with_3 = '<td class="col-result">Unreported</td>'
    # These use caching to prevent extra method calls
    DASH_PIE_PNG_1 = constants.Dashboard.get_dash_pie_1()
    DASH_PIE_PNG_2 = constants.Dashboard.get_dash_pie_2()
    DASH_PIE_PNG_3 = constants.Dashboard.get_dash_pie_3()
    find_it_4 = 'href="%s"' % DASH_PIE_PNG_1
    swap_with_4 = 'href="%s"' % DASH_PIE_PNG_2
    try:
        abs_path = os.path.abspath(".")
        dashboard_path = os.path.join(abs_path, "dashboard.html")
        # Part 1: Finalizing the dashboard / integrating html report
        if os.path.exists(dashboard_path):
            the_html_d = None
            with open(dashboard_path, "r", encoding="utf-8") as f:
                the_html_d = f.read()
            if sb_config._multithreaded and "-c" in sys_argv:
                # Threads have "-c" in sys.argv, except for the last
                raise Exception('Break out of "try" block.')
            if sb_config._multithreaded:
                dash_pie_loc = constants.Dashboard.DASH_PIE
                pie_path = os.path.join(abs_path, dash_pie_loc)
                if os.path.exists(pie_path):
                    import json

                    with open(pie_path, "r") as f:
                        dash_pie = f.read().strip()
                    sb_config._saved_dashboard_pie = json.loads(dash_pie)
            # If the test run doesn't complete by itself, stop refresh
            the_html_d = the_html_d.replace(find_it, swap_with)
            the_html_d = the_html_d.replace(find_it_2, swap_with_2)
            the_html_d = the_html_d.replace(find_it_3, swap_with_3)
            the_html_d = the_html_d.replace(find_it_4, swap_with_4)
            the_html_d += stamp
            if sb_config._dash_is_html_report and (
                sb_config._saved_dashboard_pie
            ):
                the_html_d = the_html_d.replace(
                    "<h1>dashboard.html</h1>",
                    sb_config._saved_dashboard_pie,
                )
                the_html_d = the_html_d.replace(
                    "</head>",
                    '</head><link rel="shortcut icon" '
                    'href="%s">' % DASH_PIE_PNG_3,
                )
                the_html_d = the_html_d.replace("<html>", '<html lang="en">')
                the_html_d = the_html_d.replace(
                    "<head>",
                    '<head><meta http-equiv="Content-Type" '
                    'content="text/html, charset=utf-8;">'
                    '<meta name="viewport" content="shrink-to-fit=no">',
                )
                if sb_config._dash_final_summary:
                    the_html_d += sb_config._dash_final_summary
                time.sleep(0.1)  # Add time for "livejs" to detect changes
                with open(dashboard_path, "w", encoding="utf-8") as f:
                    f.write(the_html_d)  # Finalize the dashboard
                time.sleep(0.1)  # Add time for "livejs" to detect changes
                the_html_d = the_html_d.replace(
                    "</head>", "</head><!-- Dashboard Report Done -->"
                )
            with open(dashboard_path, "w", encoding="utf-8") as f:
                f.write(the_html_d)  # Finalize the dashboard
            with suppress(Exception):
                shared_utils.make_writable(dashboard_path)
            assets_style = "./assets/style.css"
            if os.path.exists(assets_style):
                html_style = None
                with open(assets_style, "r", encoding="utf-8") as f:
                    html_style = f.read()
                if html_style:
                    html_style = html_style.replace("top: -50px;", "top: 2px;")
                    html_style = html_style.replace("+ 50px)", "+ 40px)")
                    html_style = html_style.replace("ht: 240px;", "ht: 228px;")
                    html_style = html_style.replace(
                        "- 80px);", "- 80px);\n  margin-bottom: -42px;"
                    )
                    html_style = html_style.replace(".collapsible", ".oldc")
                    html_style = html_style.replace(" (hide details)", "")
                    html_style = html_style.replace(" (show details)", "")
                with open(assets_style, "w", encoding="utf-8") as f:
                    f.write(html_style)
                with suppress(Exception):
                    shared_utils.make_writable(assets_style)
            # Part 2: Appending a pytest html report with dashboard data
            html_report_path = None
            if sb_config._html_report_name:
                html_report_path = os.path.join(
                    abs_path, sb_config._html_report_name
                )
            if sb_config._html_report_copy:
                html_report_path_copy = os.path.join(
                    abs_path, sb_config._html_report_copy
                )
            if (
                sb_config._using_html_report
                and html_report_path
                and os.path.exists(html_report_path)
                and not sb_config._dash_is_html_report
            ):
                # Add the dashboard pie to the pytest html report
                the_html_r = None
                with open(html_report_path, "r", encoding="utf-8") as f:
                    the_html_r = f.read()
                if sb_config._saved_dashboard_pie:
                    h_r_name = sb_config._html_report_name
                    if "/" in h_r_name and h_r_name.endswith(".html"):
                        h_r_name = h_r_name.split("/")[-1]
                    elif "\\" in h_r_name and h_r_name.endswith(".html"):
                        h_r_name = h_r_name.split("\\")[-1]
                    the_html_r = the_html_r.replace(
                        '<h1 id="title">%s</h1>' % h_r_name,
                        sb_config._saved_dashboard_pie,
                    )
                    the_html_r = the_html_r.replace(
                        "</head>",
                        '</head><link rel="shortcut icon" href='
                        '"%s">' % DASH_PIE_PNG_3,
                    )
                    if sb_config._dash_final_summary:
                        the_html_r += sb_config._dash_final_summary
                assets_chunk = "if (assets.length === 1) {"
                remove_media = "container.classList.remove('media-container')"
                rm_n_left = '<div class="media-container__nav--left"><</div>'
                rm_n_right = '<div class="media-container__nav--right">></div>'
                the_html_r = the_html_r.replace(
                    assets_chunk,
                    "%s %s" % (assets_chunk, remove_media),
                )
                the_html_r = the_html_r.replace(rm_n_left, "")
                the_html_r = the_html_r.replace(rm_n_right, "")
                the_html_r = the_html_r.replace("<ul>$", "$")
                the_html_r = the_html_r.replace("}<ul>", "}")
                the_html_r = the_html_r.replace(
                    "<li>${val}</li>", "${val},&nbsp;&nbsp;"
                )
                the_html_r = the_html_r.replace(
                    "<div>${value}</div>", "<span>${value}</span"
                )
                ph_link = '<a href="https://pypi.python.org/pypi/pytest-html">'
                sb_link = (
                    '<a href="https://github.com/seleniumbase/SeleniumBase">'
                    'SeleniumBase</a>'
                )
                the_html_r = the_html_r.replace(
                    ph_link, "%s and %s" % (sb_link, ph_link)
                )
                the_html_r = the_html_r.replace(
                    "findAll('.collapsible", "//findAll('.collapsible"
                )
                the_html_r = the_html_r.replace(
                    "mediaName.innerText", "//mediaName.innerText"
                )
                the_html_r = the_html_r.replace(
                    "counter.innerText", "//counter.innerText"
                )
                run_count = '<p class="run-count">'
                run_c_loc = the_html_r.find(run_count)
                rc_loc = the_html_r.find(" took ", run_c_loc)
                end_rc_loc = the_html_r.find(".</p>", rc_loc)
                run_time = "%.2f" % duration
                new_time = " ran in %s seconds" % run_time
                the_html_r = (
                    the_html_r[:rc_loc] + new_time + the_html_r[end_rc_loc:]
                )
                with open(html_report_path, "w", encoding="utf-8") as f:
                    f.write(the_html_r)  # Finalize the HTML report
                with suppress(Exception):
                    shared_utils.make_writable(html_report_path)
                with open(html_report_path_copy, "w", encoding="utf-8") as f:
                    f.write(the_html_r)  # Finalize the HTML report copy
                with suppress(Exception):
                    shared_utils.make_writable(html_report_path_copy)
    except KeyboardInterrupt:
        pass
    except Exception:
        pass


def pytest_unconfigure(config):
    """This runs after all tests have completed with pytest."""
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    reporter = config.pluginmanager.get_plugin("terminalreporter")
    if not hasattr(reporter, "_sessionstarttime"):
        return
    if hasattr(sb_config, "_multithreaded") and sb_config._multithreaded:
        import fasteners

        dash_lock = fasteners.InterProcessLock(constants.Dashboard.LOCKFILE)
        if hasattr(sb_config, "dashboard") and sb_config.dashboard:
            # Multi-threaded tests with the Dashboard
            abs_path = os.path.abspath(".")
            dash_lock_file = constants.Dashboard.LOCKFILE
            dash_lock_path = os.path.join(abs_path, dash_lock_file)
            if os.path.exists(dash_lock_path):
                sb_config._only_unittest = False
                dashboard_path = os.path.join(abs_path, "dashboard.html")
                with dash_lock:
                    if (
                        sb_config._dash_html
                        and config.getoption("htmlpath") == "dashboard.html"
                    ):
                        # Dash is HTML Report (Multithreaded)
                        sb_config._dash_is_html_report = True
                        with open(dashboard_path, "w", encoding="utf-8") as f:
                            f.write(sb_config._dash_html)
                    # Dashboard Multithreaded
                    _perform_pytest_unconfigure_(config)
                    return
            else:
                # Dash Lock is missing
                _perform_pytest_unconfigure_(config)
                return
        with dash_lock:
            # Multi-threaded tests
            _perform_pytest_unconfigure_(config)
            return
    else:
        # Single-threaded tests
        _perform_pytest_unconfigure_(config)
        return


@pytest.fixture()
def sb(request):
    """SeleniumBase as a pytest fixture.
    Usage example: "def test_one(sb):"
    You may need to use this for tests that use other pytest fixtures."""
    from seleniumbase import BaseCase
    from seleniumbase.core import session_helper

    class BaseClass(BaseCase):
        def setUp(self):
            super().setUp()

        def tearDown(self):
            self.save_teardown_screenshot()
            super().tearDown()

        def base_method(self):
            pass

    if request.cls:
        if sb_config.reuse_class_session:
            the_class = str(request.cls).split(".")[-1].split("'")[0]
            if the_class != sb_config._sb_class:
                session_helper.end_reused_class_session_as_needed()
                sb_config._sb_class = the_class
        request.cls.sb = BaseClass("base_method")
        request.cls.sb.setUp()
        request.cls.sb._needs_tearDown = True
        request.cls.sb._using_sb_fixture = True
        request.cls.sb._using_sb_fixture_class = True
        sb_config._sb_node[request.node.nodeid] = request.cls.sb
        yield request.cls.sb
        if request.cls.sb._needs_tearDown:
            request.cls.sb.tearDown()
            request.cls.sb._needs_tearDown = False
    else:
        sb = BaseClass("base_method")
        sb.setUp()
        sb._needs_tearDown = True
        sb._using_sb_fixture = True
        sb._using_sb_fixture_no_class = True
        sb_config._sb_node[request.node.nodeid] = sb
        yield sb
        if sb._needs_tearDown:
            sb.tearDown()
            sb._needs_tearDown = False


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
    if "--co" in sys_argv or "--collect-only" in sys_argv:
        return
    pytest_html = item.config.pluginmanager.getplugin("html")
    outcome = yield
    report = outcome.get_result()
    if sb_config._multithreaded:
        sb_config._using_html_report = True  # For Dashboard use
    if (
        pytest_html
        and report.when == "call"
        and hasattr(sb_config, "dashboard")
    ):
        if sb_config.dashboard and not sb_config._sbase_detected:
            test_id, display_id = _get_test_ids_(item)
            r_outcome = report.outcome
            if len(r_outcome) > 1:
                r_outcome = r_outcome[0].upper() + r_outcome[1:]
            sb_config._results[test_id] = r_outcome
            sb_config._duration[test_id] = "*****"
            sb_config._display_id[test_id] = display_id
            sb_config._d_t_log_path[test_id] = ""
            if test_id not in sb_config._extra_dash_entries:
                sb_config._extra_dash_entries.append(test_id)
        elif (
            sb_config._sbase_detected
            and ((python3_11_or_newer and py311_patch2) or "--pdb" in sys_argv)
            and (report.outcome == "failed" or "AssertionError" in str(call))
            and not sb_config._has_exception
        ):
            # Handle a bug on Python 3.11 where exceptions aren't seen
            log_path = ""
            sb_config._has_logs = True
            if hasattr(sb_config, "_test_logpath"):
                log_path = sb_config._test_logpath
            if sb_config.dashboard:
                sb_config._process_dashboard_entry(True)
            if hasattr(sb_config, "_add_pytest_html_extra"):
                sb_config._add_pytest_html_extra()
            if hasattr(sb_config, "_visual_baseline_copies"):
                sb_config._process_v_baseline_logs()
            sb_config._excinfo_value = call.excinfo.value
            sb_config._excinfo_tb = call.excinfo.tb
            if "pytest_plugin.BaseClass.base_method" not in log_path:
                source = None
                if hasattr(sb_config, "_last_page_source"):
                    source = sb_config._last_page_source
                if log_path and source:
                    log_helper.log_page_source(log_path, None, source)
                last_page_screenshot_png = None
                if hasattr(sb_config, "_last_page_screenshot_png"):
                    last_page_screenshot_png = (
                        sb_config._last_page_screenshot_png
                    )
                if log_path and last_page_screenshot_png:
                    log_helper.log_screenshot(
                        log_path, None, last_page_screenshot_png
                    )
                if log_path:
                    sb_config._log_fail_data()
        with suppress(Exception):
            extra_report = None
            if hasattr(item, "_testcase"):
                extra_report = item._testcase._html_report_extra
            elif hasattr(item.instance, "sb") or (
                item.nodeid in sb_config._sb_node
            ):
                if not hasattr(item.instance, "sb"):
                    sb_node = sb_config._sb_node[item.nodeid]
                else:
                    sb_node = item.instance.sb
                test_id = item.nodeid
                if not test_id:
                    test_id = "unidentified_TestCase"
                r"""
                # Now using the nodeid for both the test_id and display_id.
                # (This only impacts tests using The Dashboard.)
                # If there are any issues, we'll revert back to the old code.
                test_id = test_id.split("/")[-1].replace(" ", "_")
                if "[" in test_id:
                    test_id_intro = test_id.split("[")[0]
                    parameter = test_id.split("[")[1]
                    parameter = re.sub(re.compile(r"\W"), "", parameter)
                    test_id = test_id_intro + "__" + parameter
                test_id = test_id.replace("/", ".").replace("\\", ".")
                test_id = test_id.replace("::", ".").replace(".py", "")
                """
                sb_node._sb_test_identifier = test_id
                if sb_node._needs_tearDown:
                    sb_node.tearDown()
                    sb_node._needs_tearDown = False
                extra_report = sb_node._html_report_extra
            else:
                return
            extra = getattr(report, "extra", [])
            if len(extra_report) > 1 and extra_report[1]["content"]:
                report.extras = extra + extra_report
            if sb_config._dash_is_html_report:
                # If the Dashboard URL is the same as the HTML Report URL,
                # have the html report refresh back to a dashboard on update.
                refresh_updates = (
                    '<script type="text/javascript" src="%s">'
                    "</script>" % constants.Dashboard.LIVE_JS
                )
                report.extras.append(pytest_html.extras.html(refresh_updates))
